artemis-model 0.1.66__py3-none-any.whl

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,45 @@
1
+ from sqlalchemy import ForeignKey, JSON
2
+ from sqlalchemy.orm import Mapped, mapped_column, relationship
3
+
4
+ from artemis_model.base import TimeStampMixin, CustomSyncBase, CustomBase
5
+
6
+ from sqlalchemy.ext.declarative import declared_attr
7
+
8
+
9
+ class SettingMixin(TimeStampMixin):
10
+ """
11
+ Exclusion Schema
12
+ {
13
+ "genres": {
14
+ "<genre_id>": "<genre_name>",
15
+ "<genre_id>": "<genre_name>",
16
+ ...
17
+ },
18
+ "artists": {
19
+ "<artist_id>": "<artist_name>",
20
+ "<artist_id>": "<artist_name>",
21
+ ...
22
+ },
23
+ "tracks": {
24
+ "<track_id>": "<track_name>",
25
+ "<track_id>": "<track_name>",
26
+ ...
27
+ },
28
+ "exclude_pal": True/False
29
+ }
30
+ """
31
+ id: Mapped[int] = mapped_column(primary_key=True, autoincrement=True)
32
+ zone_id: Mapped[int] = mapped_column(ForeignKey("zone.id"), index=True)
33
+ exclusion: Mapped[JSON] = mapped_column(JSON, nullable=True)
34
+
35
+ @declared_attr
36
+ def zone(cls) -> Mapped["Zone"]:
37
+ return relationship(back_populates="setting")
38
+
39
+
40
+ class SettingSync(CustomSyncBase, SettingMixin):
41
+ pass
42
+
43
+
44
+ class Setting(CustomBase, SettingMixin):
45
+ pass
artemis_model/track.py ADDED
@@ -0,0 +1,123 @@
1
+ import uuid
2
+ from datetime import datetime
3
+ from typing import List
4
+
5
+ from sqlalchemy import UUID, Computed, ForeignKey, 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 TrackMixin(TimeStampMixin):
19
+ id: Mapped[uuid.UUID] = mapped_column(
20
+ UUID(as_uuid=True), primary_key=True, default=uuid.uuid4
21
+ )
22
+ name: Mapped[str] = mapped_column(nullable=False)
23
+ album_id: Mapped[uuid.UUID] = mapped_column(ForeignKey("album.id"), nullable=False)
24
+ artist_id: Mapped[uuid.UUID] = mapped_column(
25
+ ForeignKey("artist.id"), nullable=False
26
+ )
27
+ entry_date: Mapped[datetime] = mapped_column(
28
+ nullable=False, default=datetime.utcnow
29
+ )
30
+ disabled: Mapped[bool] = mapped_column(nullable=False, default=False)
31
+ legacy_id: Mapped[str] = mapped_column(nullable=False)
32
+ is_internal: Mapped[bool] = mapped_column(nullable=False, default=False)
33
+ is_parental_advisory: Mapped[bool] = mapped_column(nullable=False, default=False)
34
+ decade: Mapped[int] = mapped_column(nullable=True)
35
+ duration: Mapped[int] = mapped_column(nullable=True)
36
+
37
+ name_tsv = mapped_column(
38
+ TSVector(),
39
+ Computed("to_tsvector('english', name)", persisted=True),
40
+ )
41
+
42
+ @declared_attr
43
+ def track_playlist_associations(cls) -> Mapped[list["PlaylistTrackAssoc"]]:
44
+ return relationship(
45
+ back_populates="track", cascade="all, delete-orphan"
46
+ )
47
+
48
+ @declared_attr
49
+ def dj_set_track_associations(cls) -> Mapped[list["DjSetTrackAssoc"]]:
50
+ return relationship(
51
+ back_populates="track", cascade="all, delete-orphan"
52
+ )
53
+
54
+ @declared_attr
55
+ def album(cls) -> Mapped["Album"]:
56
+ return relationship("Album", back_populates="tracks")
57
+
58
+ @declared_attr
59
+ def artist(cls) -> Mapped["Artist"]:
60
+ return relationship("Artist", back_populates="tracks")
61
+
62
+ @declared_attr
63
+ def genres(cls) -> Mapped[List["Genre"]]:
64
+ return relationship(
65
+ "Genre", secondary="track_genre_assoc", back_populates="tracks"
66
+ )
67
+
68
+ @declared_attr
69
+ def label(cls) -> Mapped["TrackLabel"]:
70
+ return relationship(
71
+ "TrackLabel",
72
+ back_populates="track",
73
+ cascade="all, delete-orphan",
74
+ )
75
+
76
+ # __table_args__ = (
77
+ # Index("fts_ix_track_name_tsv", to_tsvector_ix("name"), postgresql_using="gin"),
78
+ # )
79
+
80
+
81
+ class TrackSync(CustomSyncBase, TrackMixin):
82
+ pass
83
+
84
+
85
+ class Track(CustomBase, TrackMixin):
86
+ pass
87
+
88
+
89
+ class TrackGenreAssocMixin:
90
+ track_id: Mapped[uuid.UUID] = mapped_column(
91
+ UUID(as_uuid=True), ForeignKey("track.id"), primary_key=True
92
+ )
93
+ genre_id: Mapped[int] = mapped_column(ForeignKey("genre.id"), primary_key=True)
94
+
95
+
96
+ class TrackGenreAssocSync(CustomSyncBase, TrackGenreAssocMixin):
97
+ pass
98
+
99
+
100
+ class TrackGenreAssoc(CustomBase, TrackGenreAssocMixin):
101
+ pass
102
+
103
+
104
+ class TrackLabelMixin:
105
+ id: Mapped[int] = mapped_column(primary_key=True, autoincrement=True)
106
+ track_id: Mapped[uuid.UUID] = mapped_column(
107
+ UUID(as_uuid=True), ForeignKey("track.id"), primary_key=True
108
+ )
109
+ name: Mapped[str] = mapped_column(nullable=False)
110
+ isrc: Mapped[str] = mapped_column(nullable=False)
111
+ legacy_id: Mapped[str] = mapped_column(nullable=False)
112
+
113
+ @declared_attr
114
+ def track(cls) -> Mapped[Track]:
115
+ return relationship("Track", back_populates="label")
116
+
117
+
118
+ class TrackLabelSync(CustomSyncBase, TrackLabelMixin):
119
+ pass
120
+
121
+
122
+ class TrackLabel(CustomBase, TrackLabelMixin):
123
+ pass
artemis_model/user.py ADDED
@@ -0,0 +1,73 @@
1
+ import uuid
2
+ from typing import List
3
+
4
+ from sqlalchemy import UUID, ForeignKey
5
+ from sqlalchemy.orm import Mapped, mapped_column, relationship
6
+ from sqlalchemy.ext.associationproxy import AssociationProxy, association_proxy
7
+ from artemis_model.base import TimeStampMixin, CustomSyncBase, CustomBase
8
+ from artemis_model.permission import UserPermissionAssoc
9
+ from sqlalchemy.ext.declarative import declared_attr
10
+
11
+
12
+ class UserMixin(TimeStampMixin):
13
+ """
14
+ This table is used to store the user itself.
15
+ """
16
+
17
+ id: Mapped[uuid.UUID] = mapped_column(
18
+ UUID(as_uuid=True), primary_key=True, default=uuid.uuid4
19
+ )
20
+ account_id: Mapped[uuid.UUID] = mapped_column(
21
+ ForeignKey("user_account.id"), nullable=False, index=True
22
+ )
23
+
24
+ @declared_attr
25
+ def user_location_associations(cls) -> Mapped[List["UserLocationAssoc"]]:
26
+ return relationship(
27
+ back_populates="user",
28
+ cascade="all, delete-orphan",
29
+ )
30
+
31
+ @declared_attr
32
+ def user_permission_associations(cls) -> Mapped[List["UserPermissionAssoc"]]:
33
+ return relationship(
34
+ cascade="all, delete-orphan",
35
+ )
36
+
37
+ @declared_attr
38
+ def account(cls) -> Mapped["UserAccount"]:
39
+ return relationship("UserAccount", back_populates="user")
40
+
41
+ @declared_attr
42
+ def organizations(cls) -> Mapped[List["Organization"]]:
43
+ return relationship(
44
+ secondary="organization_user_assoc", back_populates="users"
45
+ )
46
+
47
+ @declared_attr
48
+ def locations(cls) -> Mapped[List["Location"]]:
49
+ return relationship(
50
+ secondary="user_location_assoc", viewonly=True
51
+ )
52
+
53
+ @declared_attr
54
+ def permission_ids(cls) -> AssociationProxy[List["Permission"] | None]:
55
+ return association_proxy(
56
+ "user_permission_associations",
57
+ "permission",
58
+ creator=lambda permission_id: UserPermissionAssoc(permission_id=permission_id),
59
+ )
60
+
61
+ @declared_attr
62
+ def permissions(cls) -> Mapped[list["Permission"]]:
63
+ return relationship(
64
+ secondary="user_permission_assoc", viewonly=True
65
+ )
66
+
67
+
68
+ class UserSync(CustomSyncBase, UserMixin):
69
+ pass
70
+
71
+
72
+ class User(CustomBase, UserMixin):
73
+ pass
artemis_model/zone.py ADDED
@@ -0,0 +1,69 @@
1
+ import uuid
2
+
3
+ from sqlalchemy import UUID, Boolean, ForeignKey, Integer, String, DateTime
4
+ from sqlalchemy.orm import Mapped, mapped_column, relationship
5
+
6
+ from artemis_model.base import CustomBase, TimeStampMixin, CustomSyncBase
7
+
8
+ from sqlalchemy.ext.declarative import declared_attr
9
+
10
+
11
+ class ZoneMixin(TimeStampMixin):
12
+ id: Mapped[int] = mapped_column(Integer, autoincrement=True, primary_key=True)
13
+ name: Mapped[str] = mapped_column(String, nullable=False)
14
+ disabled: Mapped[bool] = mapped_column(Boolean, nullable=False, default=False)
15
+
16
+ # Player relations will be added later.
17
+ location_id: Mapped[uuid.UUID] = mapped_column(
18
+ UUID(as_uuid=True), ForeignKey("location.id"), index=True
19
+ )
20
+ message_frequency: Mapped[int] = mapped_column(nullable=False, default=1)
21
+ legacy_id: Mapped[uuid.UUID] = mapped_column(UUID(as_uuid=True), nullable=True)
22
+
23
+ @declared_attr
24
+ def location(cls) -> Mapped["Location"]:
25
+ return relationship(back_populates="zones")
26
+
27
+ @declared_attr
28
+ def schedules(cls) -> Mapped[list["Schedule"]]:
29
+ return relationship(back_populates="zone")
30
+
31
+ @declared_attr
32
+ def message_play_details(cşs) -> Mapped[list["MessagePlayDetail"]]:
33
+ return relationship(back_populates="zone")
34
+
35
+ @declared_attr
36
+ def license(cls) -> Mapped["License"]:
37
+ return relationship(back_populates="zone", uselist=False)
38
+
39
+ @declared_attr
40
+ def setting(cls) -> Mapped["Setting"]:
41
+ return relationship(back_populates="zone", uselist=False)
42
+
43
+
44
+ class ZoneSync(CustomSyncBase, ZoneMixin):
45
+ pass
46
+
47
+
48
+ class Zone(CustomBase, ZoneMixin):
49
+ pass
50
+
51
+
52
+ class LicenseMixin(TimeStampMixin):
53
+ id: Mapped[int] = mapped_column(autoincrement=True, primary_key=True)
54
+ zone_id: Mapped[int] = mapped_column(ForeignKey("zone.id"), nullable=False)
55
+ license_type: Mapped[str] = mapped_column(nullable=False, default="FREE")
56
+ valid_until: Mapped[DateTime] = mapped_column(DateTime, nullable=True)
57
+ expired: Mapped[bool] = mapped_column(Boolean, nullable=False, default=False)
58
+
59
+ @declared_attr
60
+ def zone(cls) -> Mapped["Zone"]:
61
+ return relationship(back_populates="license", uselist=False)
62
+
63
+
64
+ class LicenseSync(CustomSyncBase, LicenseMixin):
65
+ pass
66
+
67
+
68
+ class License(CustomBase, LicenseMixin):
69
+ pass
@@ -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,22 @@
1
+ artemis_model/__init__.py,sha256=ioJbxHgpk27FMPMDlM--9ZzSDKZet98VzW0wDjU2DqI,693
2
+ artemis_model/album.py,sha256=cnjrE0catTK24uWTrAoRcZUQB3CPt5l8s9PeM2sxLrc,2103
3
+ artemis_model/artist.py,sha256=Mx2bnl7bxqm2ELjDIePGFfIkQU_DbnfOTc507dzh14g,1577
4
+ artemis_model/auth.py,sha256=WVonoYbCE0uvRuPR2Fz1hZJ-xmbyTuBVt6H51X6OfEs,4087
5
+ artemis_model/base.py,sha256=21Isonytb_iWfmde4u8LiU6a27h7mkgnwncTAKi9ezY,6768
6
+ artemis_model/category.py,sha256=jhCZrK-LxPmc0pXmVifAZWdCENssBTzdTmC3nqqZc6s,1761
7
+ artemis_model/dj_set.py,sha256=-0rU2xOAJ4vecite8yKFmp9yFLPZurFrdFmYAjPHtso,3848
8
+ artemis_model/genre.py,sha256=oMs1ZZfr0SZWmWzW9PEPuaNcatfeIx53nk5M3DPodrQ,1615
9
+ artemis_model/location.py,sha256=T6TlUCXxGyicYtb39dw7XjbCSNECZnMPYahxtOtyEiE,4687
10
+ artemis_model/message.py,sha256=gua3X_1hS7akmS42wQGtL0I-FCJUHdTBM4n2DW4hTxQ,3280
11
+ artemis_model/organization.py,sha256=hu9jKB2qu57BwN0sUUK13p3tC-Hqw9EghFMqjFP9Pr4,2387
12
+ artemis_model/otp.py,sha256=TjZ_EHeIedjsLIqyZTtzd0s6Uc-1dleMEV8DhzsW44o,1046
13
+ artemis_model/permission.py,sha256=sEBRN-bQJl2TNtRJ5LRuySoEbW1lY4DCoyRk0nOM-kY,1503
14
+ artemis_model/playlist.py,sha256=MPdb9uKZd2f0M2A_V5OUAz-fG7u2FhN2ztlxXblYpps,5890
15
+ artemis_model/schedule.py,sha256=Noj6CAcw4xJS8wvtiLiPNvQyzjImU8wuhKo0c6c2zb4,1155
16
+ artemis_model/setting.py,sha256=McWbeDZP45km3VSSwP1BgAmmjO4qCL0pTnOmJVAz0WI,1265
17
+ artemis_model/track.py,sha256=XOSUEqGvz0XladvriWFNXdwvVKYlwvQlbcqW7E-mEuU,3774
18
+ artemis_model/user.py,sha256=ZtZVwavSPoQmYM1_MarMULy-DJa2pH482G_G8su0b6c,2211
19
+ artemis_model/zone.py,sha256=iGRUtzUwKh9LHT3MOfzzg1DnkPBts_ZBzZVTi2EmIgs,2282
20
+ artemis_model-0.1.66.dist-info/METADATA,sha256=M9JkDUwKqlXod4ST3wWslrD1RdpkAbe_8nlOesjoGFk,3351
21
+ artemis_model-0.1.66.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
22
+ artemis_model-0.1.66.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: poetry-core 1.9.0
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any