artemis-model 0.1.66__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,113 @@
1
+ Metadata-Version: 2.1
2
+ Name: artemis-model
3
+ Version: 0.1.66
4
+ Summary:
5
+ Author: Jukeboxy
6
+ Requires-Python: >=3.10.6,<4.0.0
7
+ Classifier: Programming Language :: Python :: 3
8
+ Classifier: Programming Language :: Python :: 3.11
9
+ Classifier: Programming Language :: Python :: 3.12
10
+ Requires-Dist: alembic (>=1.13.1,<2.0.0)
11
+ Requires-Dist: asyncpg (>=0.29.0,<0.30.0)
12
+ Requires-Dist: greenlet (>=3.0.2,<4.0.0)
13
+ Requires-Dist: python-slugify (>=8.0.4,<9.0.0)
14
+ Requires-Dist: sqlalchemy (>=2.0.23,<3.0.0)
15
+ Description-Content-Type: text/markdown
16
+
17
+ # artemis-model
18
+
19
+ Welcome to `artemis-model`, the backbone repository that contains all the essential models used in both the `artemis-api` and the `prophet` project. This project includes asynchronous models used by `artemis-api`, such as `Artist`, and synchronous models like `ArtistSync` for other implementations.
20
+
21
+ ## Getting Started
22
+
23
+ To set up your development environment for `artemis-model`, follow these initial setup steps:
24
+
25
+ 1. **Environment Setup**
26
+
27
+ ```shell
28
+ cp ./alembic/.env.example ./alembic/.env
29
+ ```
30
+
31
+ After copying the example environment file, make sure to fill in the `.env` file with your specific configurations.
32
+
33
+ 2. **Install Dependencies**
34
+
35
+ ```shell
36
+ poetry install --all-extras
37
+ ```
38
+
39
+ This will install all necessary dependencies to get you started with `artemis-models` and `alembic`.
40
+
41
+ ## Creating a New Model
42
+
43
+ To introduce a new model in the project, you should start by creating a mixin and then define two different model classes that inherit from this mixin.
44
+
45
+ ### Example: Adding a `LoginHistory` Model
46
+
47
+ 1. **Define the Mixin**
48
+ This mixin will include all the common attributes of your model.
49
+
50
+ ```python
51
+ class LoginHistoryMixin:
52
+ """
53
+ Stores the login history of users.
54
+ """
55
+
56
+ id: Mapped[int] = mapped_column(primary_key=True, autoincrement=True, nullable=False)
57
+ account_id: Mapped[uuid.UUID] = mapped_column(ForeignKey("user_account.id"), nullable=False, index=True)
58
+ ip_address: Mapped[str] = mapped_column(nullable=True)
59
+ created_at = mapped_column(DateTime, default=datetime.utcnow)
60
+
61
+ @declared_attr
62
+ def account(cls) -> Mapped["UserAccount"]:
63
+ return relationship("UserAccount", back_populates="login_histories")
64
+ ```
65
+
66
+ 2. **Inherit from Base Classes**
67
+ Create two classes that inherit from CustomBase and CustomSyncBase respectively, using the mixin for shared attributes.
68
+
69
+ ```python
70
+ class LoginHistorySync(CustomSyncBase, LoginHistoryMixin):
71
+ pass
72
+
73
+ class LoginHistory(CustomBase, LoginHistoryMixin):
74
+ pass
75
+ ```
76
+
77
+ ## Version Management and Builds
78
+
79
+ 1. **Update the Project Version**
80
+
81
+ Open pyproject.toml and increment the minor version number.
82
+
83
+ 2. **Build the Project**
84
+
85
+ ```shell
86
+ poetry build
87
+ ```
88
+
89
+ 3. **Update Dependency in artemis-api/prophet**
90
+
91
+ If the build succeeds, remember to also bump the version number in the pyproject.toml of artemis-api and prophet to match the new version of artemis-model.
92
+
93
+ ## Using Alembic for Model Changes
94
+
95
+ If modifications are necessary for any model:
96
+
97
+ 1. **Modify the Model**
98
+ 2. **Create an Alembic Revision**
99
+
100
+ ```shell
101
+ alembic revision --autogenerate -m "Description of changes"
102
+ ```
103
+
104
+ 3. **Upgrade Database Schema**
105
+
106
+ ```shell
107
+ alembic upgrade head
108
+ ```
109
+
110
+ Ensure that the new Alembic script in the versions directory is committed to your git repository.
111
+ Repeat the build and version update steps as necessary after making changes.
112
+
113
+
@@ -0,0 +1,96 @@
1
+ # artemis-model
2
+
3
+ Welcome to `artemis-model`, the backbone repository that contains all the essential models used in both the `artemis-api` and the `prophet` project. This project includes asynchronous models used by `artemis-api`, such as `Artist`, and synchronous models like `ArtistSync` for other implementations.
4
+
5
+ ## Getting Started
6
+
7
+ To set up your development environment for `artemis-model`, follow these initial setup steps:
8
+
9
+ 1. **Environment Setup**
10
+
11
+ ```shell
12
+ cp ./alembic/.env.example ./alembic/.env
13
+ ```
14
+
15
+ After copying the example environment file, make sure to fill in the `.env` file with your specific configurations.
16
+
17
+ 2. **Install Dependencies**
18
+
19
+ ```shell
20
+ poetry install --all-extras
21
+ ```
22
+
23
+ This will install all necessary dependencies to get you started with `artemis-models` and `alembic`.
24
+
25
+ ## Creating a New Model
26
+
27
+ To introduce a new model in the project, you should start by creating a mixin and then define two different model classes that inherit from this mixin.
28
+
29
+ ### Example: Adding a `LoginHistory` Model
30
+
31
+ 1. **Define the Mixin**
32
+ This mixin will include all the common attributes of your model.
33
+
34
+ ```python
35
+ class LoginHistoryMixin:
36
+ """
37
+ Stores the login history of users.
38
+ """
39
+
40
+ id: Mapped[int] = mapped_column(primary_key=True, autoincrement=True, nullable=False)
41
+ account_id: Mapped[uuid.UUID] = mapped_column(ForeignKey("user_account.id"), nullable=False, index=True)
42
+ ip_address: Mapped[str] = mapped_column(nullable=True)
43
+ created_at = mapped_column(DateTime, default=datetime.utcnow)
44
+
45
+ @declared_attr
46
+ def account(cls) -> Mapped["UserAccount"]:
47
+ return relationship("UserAccount", back_populates="login_histories")
48
+ ```
49
+
50
+ 2. **Inherit from Base Classes**
51
+ Create two classes that inherit from CustomBase and CustomSyncBase respectively, using the mixin for shared attributes.
52
+
53
+ ```python
54
+ class LoginHistorySync(CustomSyncBase, LoginHistoryMixin):
55
+ pass
56
+
57
+ class LoginHistory(CustomBase, LoginHistoryMixin):
58
+ pass
59
+ ```
60
+
61
+ ## Version Management and Builds
62
+
63
+ 1. **Update the Project Version**
64
+
65
+ Open pyproject.toml and increment the minor version number.
66
+
67
+ 2. **Build the Project**
68
+
69
+ ```shell
70
+ poetry build
71
+ ```
72
+
73
+ 3. **Update Dependency in artemis-api/prophet**
74
+
75
+ If the build succeeds, remember to also bump the version number in the pyproject.toml of artemis-api and prophet to match the new version of artemis-model.
76
+
77
+ ## Using Alembic for Model Changes
78
+
79
+ If modifications are necessary for any model:
80
+
81
+ 1. **Modify the Model**
82
+ 2. **Create an Alembic Revision**
83
+
84
+ ```shell
85
+ alembic revision --autogenerate -m "Description of changes"
86
+ ```
87
+
88
+ 3. **Upgrade Database Schema**
89
+
90
+ ```shell
91
+ alembic upgrade head
92
+ ```
93
+
94
+ Ensure that the new Alembic script in the versions directory is committed to your git repository.
95
+ Repeat the build and version update steps as necessary after making changes.
96
+
@@ -0,0 +1,17 @@
1
+ from artemis_model.album import * # noqa
2
+ from artemis_model.artist import * # noqa
3
+ from artemis_model.auth import * # noqa
4
+ from artemis_model.category import * # noqa
5
+ from artemis_model.dj_set import * # noqa
6
+ from artemis_model.genre import * # noqa
7
+ from artemis_model.location import * # noqa
8
+ from artemis_model.message import * # noqa
9
+ from artemis_model.organization import * # noqa
10
+ from artemis_model.playlist import * # noqa
11
+ from artemis_model.schedule import * # noqa
12
+ from artemis_model.setting import * # noqa
13
+ from artemis_model.track import * # noqa
14
+ from artemis_model.user import * # noqa
15
+ from artemis_model.zone import * # noqa
16
+ from artemis_model.otp import * # noqa
17
+
@@ -0,0 +1,73 @@
1
+ """Album models"""
2
+
3
+ from datetime import datetime
4
+
5
+ from sqlalchemy import Computed, ForeignKey, func, literal, text
6
+ from sqlalchemy.orm import Mapped, mapped_column, relationship
7
+
8
+ from artemis_model.base import CustomSyncBase, CustomBase, TSVector, TimeStampMixin
9
+
10
+ from sqlalchemy.ext.declarative import declared_attr
11
+
12
+
13
+ def to_tsvector_ix(*columns):
14
+ s = " || ' ' || ".join(columns)
15
+ return func.to_tsvector(literal("english"), text(s))
16
+
17
+
18
+ class AlbumMixin(TimeStampMixin):
19
+ id: Mapped[int] = mapped_column(primary_key=True, autoincrement=True)
20
+ name: Mapped[str] = mapped_column(nullable=False)
21
+ description: Mapped[str | None] = mapped_column(nullable=True)
22
+ entry_date: Mapped[datetime] = mapped_column(
23
+ nullable=False, default=datetime.utcnow
24
+ )
25
+ disabled: Mapped[bool] = mapped_column(nullable=False, default=False)
26
+ legacy_id: Mapped[str] = mapped_column(nullable=False)
27
+ is_internal: Mapped[bool] = mapped_column(nullable=False, default=False)
28
+
29
+ name_tsv = mapped_column(
30
+ TSVector(),
31
+ Computed("to_tsvector('english', name)", persisted=True),
32
+ )
33
+
34
+ # __table_args__ = (
35
+ # Index("fts_ix_album_name_tsv", to_tsvector_ix("name"), postgresql_using="gin"),
36
+ # )
37
+
38
+ @declared_attr
39
+ def artists(cls) -> Mapped[list["Artist"]]:
40
+ return relationship(secondary="album_artist_assoc", back_populates="albums")
41
+
42
+ @declared_attr
43
+ def tracks(cls) -> Mapped[list["Track"]]:
44
+ return relationship(
45
+ "Track",
46
+ back_populates="album",
47
+ cascade="all, delete-orphan",
48
+ )
49
+
50
+
51
+ class AlbumSync(CustomSyncBase, AlbumMixin):
52
+ pass
53
+
54
+
55
+ class Album(CustomBase, AlbumMixin):
56
+ pass
57
+
58
+
59
+ class AlbumArtistAssocMixin():
60
+ album_id: Mapped[int] = mapped_column(
61
+ ForeignKey("album.id"), primary_key=True, nullable=False
62
+ )
63
+ artist_id: Mapped[int] = mapped_column(
64
+ ForeignKey("artist.id"), primary_key=True, nullable=False
65
+ )
66
+
67
+
68
+ class AlbumArtistAssocSync(CustomSyncBase, AlbumArtistAssocMixin):
69
+ pass
70
+
71
+
72
+ class AlbumArtistAssoc(CustomBase, AlbumArtistAssocMixin):
73
+ pass
@@ -0,0 +1,51 @@
1
+ """Artist models"""
2
+
3
+ from datetime import datetime
4
+
5
+ from sqlalchemy import Computed, func, literal, text
6
+ from sqlalchemy.orm import Mapped, mapped_column, relationship
7
+
8
+ from artemis_model.base import CustomBase, CustomSyncBase, TSVector, TimeStampMixin
9
+
10
+ from sqlalchemy.ext.declarative import declared_attr
11
+
12
+
13
+ def to_tsvector_ix(*columns):
14
+ s = " || ' ' || ".join(columns)
15
+ return func.to_tsvector(literal("english"), text(s))
16
+
17
+
18
+ class ArtistMixin(TimeStampMixin):
19
+ id: Mapped[int] = mapped_column(primary_key=True, autoincrement=True)
20
+ name: Mapped[str] = mapped_column(nullable=False)
21
+ entry_date: Mapped[datetime] = mapped_column(
22
+ nullable=False, default=datetime.utcnow
23
+ )
24
+ disabled: Mapped[bool] = mapped_column(nullable=False, default=False)
25
+ is_internal: Mapped[bool] = mapped_column(nullable=False, default=False)
26
+ legacy_id: Mapped[str] = mapped_column(nullable=False)
27
+
28
+ name_tsv = mapped_column(
29
+ TSVector(),
30
+ Computed("to_tsvector('english', name)", persisted=True),
31
+ )
32
+
33
+ # __table_args__ = (
34
+ # Index("fts_ix_artist_name_tsv", to_tsvector_ix("name"), postgresql_using="gin"),
35
+ # )
36
+
37
+ @declared_attr
38
+ def albums(cls) -> Mapped[list["Album"]]:
39
+ return relationship(secondary="album_artist_assoc", back_populates="artists")
40
+
41
+ @declared_attr
42
+ def tracks(cls) -> Mapped[list["Track"]]:
43
+ return relationship("Track", back_populates="artist", cascade="all, delete-orphan")
44
+
45
+
46
+ class ArtistSync(CustomSyncBase, ArtistMixin):
47
+ pass
48
+
49
+
50
+ class Artist(CustomBase, ArtistMixin):
51
+ pass
@@ -0,0 +1,119 @@
1
+ """Auth models."""
2
+ import uuid
3
+ from datetime import datetime
4
+ from typing import Optional
5
+
6
+ from sqlalchemy import UUID, DateTime, ForeignKey, LargeBinary
7
+ from sqlalchemy.orm import Mapped, mapped_column, relationship
8
+ from sqlalchemy.ext.declarative import declared_attr
9
+
10
+ from artemis_model.base import CustomSyncBase, TimeStampMixin, CustomBase
11
+
12
+
13
+ class UserUnverifiedAccountMixin(TimeStampMixin):
14
+ """
15
+ This table is used to store the account info of users who have requested an account but have not yet
16
+ been verified. Once the user has been verified, the account will be moved to the UserAccount table.
17
+ """
18
+
19
+ id: Mapped[uuid.UUID] = mapped_column(
20
+ UUID(as_uuid=True), primary_key=True, default=uuid.uuid4
21
+ )
22
+ email: Mapped[str] = mapped_column(index=True)
23
+ name: Mapped[str] = mapped_column(nullable=False)
24
+ mobile: Mapped[str] = mapped_column(nullable=True, index=True)
25
+ is_root_user: Mapped[bool] = mapped_column(default=True)
26
+ is_email_verified: Mapped[bool] = mapped_column(default=False)
27
+ is_mobile_verified: Mapped[bool] = mapped_column(default=False)
28
+ is_onboarded: Mapped[bool] = mapped_column(default=False)
29
+ password = mapped_column(LargeBinary, nullable=False)
30
+ provider: Mapped[str] = mapped_column(default="internal")
31
+
32
+
33
+ class UserUnverifiedAccountSync(CustomSyncBase, UserUnverifiedAccountMixin):
34
+ pass
35
+
36
+
37
+ class UserUnverifiedAccount(CustomBase, UserUnverifiedAccountMixin):
38
+ pass
39
+
40
+
41
+ class UserAccountMixin(TimeStampMixin):
42
+ """
43
+ This table is used to store the account info of users who have been verified.
44
+ """
45
+
46
+ id: Mapped[uuid.UUID] = mapped_column(
47
+ UUID(as_uuid=True), primary_key=True, default=uuid.uuid4
48
+ )
49
+ email: Mapped[str] = mapped_column(index=True, unique=True)
50
+ name: Mapped[str] = mapped_column(nullable=False)
51
+ mobile: Mapped[Optional[str]] = mapped_column(
52
+ nullable=True, unique=True, index=True
53
+ )
54
+ password = mapped_column(LargeBinary, nullable=True)
55
+ provider: Mapped[str] = mapped_column(nullable=False)
56
+ oauth_id: Mapped[Optional[str]] = mapped_column(nullable=True, index=True)
57
+ image_url: Mapped[Optional[str]] = mapped_column(nullable=True)
58
+ is_email_verified: Mapped[bool] = mapped_column(default=False)
59
+ is_mobile_verified: Mapped[bool] = mapped_column(default=False)
60
+ is_root_user: Mapped[bool] = mapped_column(default=True)
61
+ is_super_admin: Mapped[bool] = mapped_column(default=False)
62
+ disabled: Mapped[bool] = mapped_column(default=False)
63
+ disabled_reason: Mapped[Optional[str]] = mapped_column(nullable=True)
64
+ is_onboarded: Mapped[bool] = mapped_column(default=False)
65
+
66
+ @declared_attr
67
+ def login_histories(cls) -> Mapped["LoginHistory"]:
68
+ return relationship("LoginHistory", back_populates="account")
69
+
70
+ @declared_attr
71
+ def user(cls) -> Mapped["User"]:
72
+ return relationship("User", back_populates="account")
73
+
74
+
75
+ class UserAccountSync(CustomSyncBase, UserAccountMixin):
76
+ pass
77
+
78
+
79
+ class UserAccount(CustomBase, UserAccountMixin):
80
+ pass
81
+
82
+
83
+ class LoginHistoryMixin:
84
+ """
85
+ This table is used to store the login history of users.
86
+ """
87
+ id: Mapped[int] = mapped_column(
88
+ primary_key=True, autoincrement=True, nullable=False
89
+ )
90
+ account_id: Mapped[uuid.UUID] = mapped_column(
91
+ ForeignKey("user_account.id"), nullable=False, index=True
92
+ )
93
+ ip_address: Mapped[str] = mapped_column(nullable=True)
94
+ created_at = mapped_column(DateTime, default=datetime.utcnow)
95
+
96
+ @declared_attr
97
+ def account(cls) -> Mapped["UserAccount"]:
98
+ return relationship("UserAccount", back_populates="login_histories")
99
+
100
+
101
+ class LoginHistorySync(CustomSyncBase, LoginHistoryMixin):
102
+ pass
103
+
104
+
105
+ class LoginHistory(CustomBase, LoginHistoryMixin):
106
+ pass
107
+
108
+
109
+ class OAuthCsrfStateMixin(TimeStampMixin):
110
+ id: Mapped[str] = mapped_column(primary_key=True, unique=True)
111
+ client_base_url: Mapped[str] = mapped_column(nullable=True)
112
+
113
+
114
+ class OAuthCsrfStateSync(CustomSyncBase, OAuthCsrfStateMixin):
115
+ pass
116
+
117
+
118
+ class OAuthCsrfState(CustomBase, OAuthCsrfStateMixin):
119
+ pass
@@ -0,0 +1,222 @@
1
+ import re
2
+ from datetime import datetime
3
+ from typing import Any
4
+
5
+ from sqlalchemy.orm import (DeclarativeBase, Mapped, declared_attr,
6
+ mapped_column, object_session, relationship)
7
+ from sqlalchemy.ext.asyncio import AsyncAttrs
8
+ from sqlalchemy import Column, Uuid, event, inspect, TypeDecorator
9
+ from sqlalchemy.dialects.postgresql import TSVECTOR
10
+
11
+
12
+ def resolve_table_name(name: str) -> str:
13
+ """Resolves table names to their mapped names."""
14
+ names = re.split("(?=[A-Z])", name) # noqa
15
+ print(names)
16
+ return "_".join([x.lower() for x in names if x])
17
+
18
+
19
+ class CustomBase(DeclarativeBase, AsyncAttrs):
20
+ __repr_attrs__: list[Any] = []
21
+ __repr_max_length__ = 15
22
+
23
+ @declared_attr
24
+ def __tablename__(self) -> str:
25
+ return resolve_table_name(self.__name__)
26
+
27
+ def dict(self) -> dict:
28
+ """Returns a dict representation of a model."""
29
+ return {c.name: getattr(self, c.name) for c in self.__table__.columns}
30
+
31
+ @property
32
+ def _id_str(self) -> str:
33
+ ids = inspect(self).identity
34
+ if ids:
35
+ return "-".join([str(x) for x in ids]) if len(ids) > 1 else str(ids[0])
36
+ else:
37
+ return "None"
38
+
39
+ @property
40
+ def _repr_attrs_str(self) -> str:
41
+ max_length = self.__repr_max_length__
42
+
43
+ values = []
44
+ single = len(self.__repr_attrs__) == 1
45
+ for key in self.__repr_attrs__:
46
+ if not hasattr(self, key):
47
+ raise KeyError(
48
+ "{} has incorrect attribute '{}' in "
49
+ "__repr__attrs__".format(self.__class__, key)
50
+ )
51
+ value = getattr(self, key)
52
+ wrap_in_quote = isinstance(value, str)
53
+
54
+ value = str(value)
55
+ if len(value) > max_length:
56
+ value = value[:max_length] + "..."
57
+
58
+ if wrap_in_quote:
59
+ value = "'{}'".format(value)
60
+ values.append(value if single else "{}:{}".format(key, value))
61
+
62
+ return " ".join(values)
63
+
64
+ def __repr__(self) -> str:
65
+ # get id like '#123'
66
+ id_str = ("#" + self._id_str) if self._id_str else ""
67
+ # join class name, id and repr_attrs
68
+ return "<{} {}{}>".format(
69
+ self.__class__.__name__,
70
+ id_str,
71
+ " " + self._repr_attrs_str if self._repr_attrs_str else "",
72
+ )
73
+
74
+
75
+ class AuditMixin(object):
76
+ created_by = Column(Uuid, nullable=True)
77
+ updated_by = Column(Uuid, nullable=True)
78
+
79
+ @declared_attr
80
+ def created_by_user(cls):
81
+ return relationship(
82
+ "User",
83
+ foreign_keys=[cls.created_by],
84
+ primaryjoin="User.id==%s.created_by" % cls.__name__,
85
+ )
86
+
87
+ @declared_attr
88
+ def updated_by_user(cls):
89
+ return relationship(
90
+ "User",
91
+ foreign_keys=[cls.updated_by],
92
+ primaryjoin="User.id==%s.updated_by" % cls.__name__,
93
+ )
94
+
95
+ @staticmethod
96
+ def _updated_info(mapper: Any, connection: Any, target: Any) -> None:
97
+ s = object_session(target)
98
+ target.updated_at = datetime.utcnow()
99
+ if user_id := getattr(s, "user_id", None):
100
+ target.updated_by = user_id
101
+
102
+ @staticmethod
103
+ def _created_info(mapper: Any, connection: Any, target: Any) -> None:
104
+ s = object_session(target)
105
+ if user_id := getattr(s, "user_id", None):
106
+ target.created_by = user_id
107
+
108
+ @classmethod
109
+ def get_session_user_id(cls, connection):
110
+ return connection.info.get("user_id")
111
+
112
+ @classmethod
113
+ def __declare_last__(cls) -> None:
114
+ event.listen(cls, "before_insert", cls._created_info)
115
+ event.listen(cls, "before_update", cls._updated_info)
116
+
117
+
118
+ class TimeStampMixin(object):
119
+ """Timestamping mixin"""
120
+
121
+ created_at: Mapped[datetime] = mapped_column(default=datetime.utcnow)
122
+ created_at._creation_order = 9998
123
+ updated_at: Mapped[datetime] = mapped_column(default=datetime.utcnow)
124
+ updated_at._creation_order = 9998
125
+
126
+ @staticmethod
127
+ def _updated_at(mapper, connection, target):
128
+ target.updated_at = datetime.utcnow()
129
+
130
+ @classmethod
131
+ def __declare_last__(cls):
132
+ event.listen(cls, "before_update", cls._updated_at)
133
+
134
+
135
+ # class InheritanceMixin(object):
136
+ # @declared_attr
137
+ # def __mapper_args__(cls):
138
+ # return {
139
+ # 'polymorphic_identity': cls.__name__.lower(),
140
+ # }
141
+
142
+
143
+ # class CustomBase(Base, AsyncAttrs):
144
+ # pass
145
+
146
+
147
+ # class SyncInheritanceMixin(object):
148
+ # type = Column(String(50))
149
+
150
+ # @declared_attr
151
+ # def __mapper_args__(cls):
152
+ # return {
153
+ # 'polymorphic_identity': cls.__name__.lower(),
154
+ # 'polymorphic_on': 'type',
155
+ # }
156
+
157
+
158
+ class CustomSyncBase(DeclarativeBase):
159
+ __repr_attrs__: list[Any] = []
160
+ __repr_max_length__ = 15
161
+ __abstract__ = True
162
+
163
+ @declared_attr
164
+ def __tablename__(self) -> str:
165
+ return resolve_table_name(self.__name__)
166
+
167
+ """ Is this missed here or not moved intentionally?"""
168
+
169
+ # def serializable_dict(self) -> Dict[str, Any]:
170
+ # d = {col.name: getattr(self, col.name) for col in self.__table__.columns}
171
+ # return orjson.loads(orjson.dumps(jsonable_encoder(d)))
172
+
173
+ def dict(self) -> dict:
174
+ """Returns a dict representation of a model."""
175
+ return {c.name: getattr(self, c.name) for c in self.__table__.columns}
176
+
177
+ @property
178
+ def _id_str(self) -> str:
179
+ ids = inspect(self).identity
180
+ if ids:
181
+ return "-".join([str(x) for x in ids]) if len(ids) > 1 else str(ids[0])
182
+ else:
183
+ return "None"
184
+
185
+ @property
186
+ def _repr_attrs_str(self) -> str:
187
+ max_length = self.__repr_max_length__
188
+
189
+ values = []
190
+ single = len(self.__repr_attrs__) == 1
191
+ for key in self.__repr_attrs__:
192
+ if not hasattr(self, key):
193
+ raise KeyError(
194
+ "{} has incorrect attribute '{}' in "
195
+ "__repr__attrs__".format(self.__class__, key)
196
+ )
197
+ value = getattr(self, key)
198
+ wrap_in_quote = isinstance(value, str)
199
+
200
+ value = str(value)
201
+ if len(value) > max_length:
202
+ value = value[:max_length] + "..."
203
+
204
+ if wrap_in_quote:
205
+ value = "'{}'".format(value)
206
+ values.append(value if single else "{}:{}".format(key, value))
207
+
208
+ return " ".join(values)
209
+
210
+ def __repr__(self) -> str:
211
+ # get id like '#123'
212
+ id_str = ("#" + self._id_str) if self._id_str else ""
213
+ # join class name, id and repr_attrs
214
+ return "<{} {}{}>".format(
215
+ self.__class__.__name__,
216
+ id_str,
217
+ " " + self._repr_attrs_str if self._repr_attrs_str else "",
218
+ )
219
+
220
+
221
+ class TSVector(TypeDecorator):
222
+ impl = TSVECTOR