Skip to content

Commit 26767d1

Browse files
SQLAlchemy 2.0 (#78)
1 parent d13a5de commit 26767d1

15 files changed

Lines changed: 126 additions & 92 deletions

File tree

calendar_backend/models/base.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
from sqlalchemy import Column, Integer, not_
77
from sqlalchemy.exc import NoResultFound
88
from sqlalchemy.ext.declarative import as_declarative, declared_attr
9-
from sqlalchemy.orm import Query, Session
9+
from sqlalchemy.orm import Query, Session, Mapped, mapped_column
1010

1111
from calendar_backend.exceptions import ObjectNotFound
1212

@@ -31,7 +31,7 @@ def __tablename__(cls) -> str: # pylint: disable=no-self-argument
3131

3232
class BaseDbModel(DeclarativeBase):
3333
__abstract__ = True
34-
id = Column(Integer, primary_key=True)
34+
id: Mapped[int] = mapped_column(Integer, primary_key=True)
3535

3636
def __repr__(self):
3737
attrs = []

calendar_backend/models/db.py

Lines changed: 66 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -5,26 +5,24 @@
55
from datetime import datetime
66
from enum import Enum
77

8-
import sqlalchemy
9-
from sqlalchemy import Column
8+
from sqlalchemy import and_, or_, Integer, String, Boolean, JSON, DateTime, Text, ForeignKey, true
109
from sqlalchemy import Enum as DbEnum
11-
from sqlalchemy import and_, or_
1210
from sqlalchemy.ext.hybrid import hybrid_method, hybrid_property
13-
from sqlalchemy.orm import relationship
11+
from sqlalchemy.orm import relationship, Mapped, mapped_column
1412

1513
from .base import BaseDbModel, ApproveStatuses
1614

1715

1816
class Credentials(BaseDbModel):
1917
"""User credentials"""
2018

21-
id = Column(sqlalchemy.Integer, primary_key=True)
22-
group = Column(sqlalchemy.String, nullable=False)
23-
email = Column(sqlalchemy.String, nullable=False)
24-
scope = Column(sqlalchemy.JSON, nullable=False)
25-
token = Column(sqlalchemy.JSON, nullable=False)
26-
create_ts = Column(sqlalchemy.DateTime, nullable=False, default=datetime.utcnow)
27-
update_ts = Column(sqlalchemy.DateTime, nullable=False, default=datetime.utcnow, onupdate=datetime.utcnow)
19+
id: Mapped[int] = mapped_column(Integer, primary_key=True)
20+
group: Mapped[int] = mapped_column(String, nullable=False)
21+
email: Mapped[int] = mapped_column(String, nullable=False)
22+
scope: Mapped[int] = mapped_column(JSON, nullable=False)
23+
token: Mapped[int] = mapped_column(JSON, nullable=False)
24+
create_ts: Mapped[int] = mapped_column(DateTime, nullable=False, default=datetime.utcnow)
25+
update_ts: Mapped[int] = mapped_column(DateTime, nullable=False, default=datetime.utcnow, onupdate=datetime.utcnow)
2826

2927

3028
class Direction(str, Enum):
@@ -33,12 +31,12 @@ class Direction(str, Enum):
3331

3432

3533
class Room(BaseDbModel):
36-
name = sqlalchemy.Column(sqlalchemy.String, nullable=False, unique=True)
37-
direction = sqlalchemy.Column(DbEnum(Direction, native_enum=False), nullable=True)
38-
building = sqlalchemy.Column(sqlalchemy.String)
39-
is_deleted = sqlalchemy.Column(sqlalchemy.Boolean, default=False)
34+
name: Mapped[int] = mapped_column(String, nullable=False, unique=True)
35+
direction: Mapped[int] = mapped_column(DbEnum(Direction, native_enum=False), nullable=True)
36+
building: Mapped[int] = mapped_column(String)
37+
is_deleted: Mapped[int] = mapped_column(Boolean, default=False)
4038

41-
events: list[Event] = relationship(
39+
events: Mapped[list[Event]] = relationship(
4240
"Event",
4341
back_populates="room",
4442
secondary="events_rooms",
@@ -48,34 +46,34 @@ class Room(BaseDbModel):
4846

4947

5048
class Lecturer(BaseDbModel):
51-
first_name = sqlalchemy.Column(sqlalchemy.String, nullable=False)
52-
middle_name = sqlalchemy.Column(sqlalchemy.String, nullable=False)
53-
last_name = sqlalchemy.Column(sqlalchemy.String, nullable=False)
54-
avatar_id = sqlalchemy.Column(sqlalchemy.Integer, sqlalchemy.ForeignKey("photo.id"))
55-
description = sqlalchemy.Column(sqlalchemy.Text, nullable=True)
56-
is_deleted = sqlalchemy.Column(sqlalchemy.Boolean, default=False)
57-
58-
avatar: Photo = relationship(
49+
first_name: Mapped[int] = mapped_column(String, nullable=False)
50+
middle_name: Mapped[int] = mapped_column(String, nullable=False)
51+
last_name: Mapped[int] = mapped_column(String, nullable=False)
52+
avatar_id: Mapped[int] = mapped_column(Integer, ForeignKey("photo.id"))
53+
description: Mapped[int] = mapped_column(Text, nullable=True)
54+
is_deleted: Mapped[int] = mapped_column(Boolean, default=False)
55+
56+
avatar: Mapped[Photo] = relationship(
5957
"Photo",
6058
foreign_keys="Lecturer.avatar_id",
6159
backref="is_avatar_for",
6260
primaryjoin="and_(Lecturer.avatar_id==Photo.id, not_(Photo.is_deleted))",
6361
)
64-
photos: list[Photo] = relationship(
62+
photos: Mapped[list[Photo]] = relationship(
6563
"Photo",
6664
back_populates="lecturer",
6765
foreign_keys="Photo.lecturer_id",
6866
order_by="Photo.id",
6967
primaryjoin="and_(Lecturer.id==Photo.lecturer_id, not_(Photo.is_deleted), Photo.approve_status=='APPROVED')",
7068
)
71-
events: list[Event] = relationship(
69+
events: Mapped[list[Event]] = relationship(
7270
"Event",
7371
secondary="events_lecturers",
7472
order_by="(Event.start_ts)",
7573
back_populates="lecturer",
7674
secondaryjoin="and_(Event.id==EventsLecturers.event_id, not_(Event.is_deleted))",
7775
)
78-
comments: list[CommentLecturer] = relationship(
76+
comments: Mapped[list[CommentLecturer]] = relationship(
7977
"CommentLecturer",
8078
back_populates="lecturer",
8179
foreign_keys="CommentLecturer.lecturer_id",
@@ -84,7 +82,7 @@ class Lecturer(BaseDbModel):
8482

8583
@hybrid_method
8684
def search(self, query: str) -> bool:
87-
response = sqlalchemy.true
85+
response = true
8886
query = query.split(' ')
8987
for q in query:
9088
response = and_(
@@ -98,11 +96,11 @@ def last_photo(self) -> Photo | None:
9896

9997

10098
class Group(BaseDbModel):
101-
name = sqlalchemy.Column(sqlalchemy.String, nullable=False)
102-
number = sqlalchemy.Column(sqlalchemy.String, nullable=False, unique=True)
103-
is_deleted = sqlalchemy.Column(sqlalchemy.Boolean, default=False)
99+
name: Mapped[int] = mapped_column(String, nullable=False)
100+
number: Mapped[int] = mapped_column(String, nullable=False, unique=True)
101+
is_deleted: Mapped[int] = mapped_column(Boolean, default=False)
104102

105-
events: list[Event] = relationship(
103+
events: Mapped[list[Event]] = relationship(
106104
"Event",
107105
foreign_keys="Event.group_id",
108106
order_by="(Event.start_ts)",
@@ -111,31 +109,31 @@ class Group(BaseDbModel):
111109

112110

113111
class Event(BaseDbModel):
114-
name = sqlalchemy.Column(sqlalchemy.String, nullable=False)
115-
group_id = sqlalchemy.Column(sqlalchemy.Integer, sqlalchemy.ForeignKey("group.id"))
116-
start_ts = sqlalchemy.Column(sqlalchemy.DateTime, nullable=False)
117-
end_ts = sqlalchemy.Column(sqlalchemy.DateTime, nullable=False)
118-
is_deleted = sqlalchemy.Column(sqlalchemy.Boolean, default=False)
112+
name: Mapped[int] = mapped_column(String, nullable=False)
113+
group_id: Mapped[int] = mapped_column(Integer, ForeignKey("group.id"))
114+
start_ts: Mapped[int] = mapped_column(DateTime, nullable=False)
115+
end_ts: Mapped[int] = mapped_column(DateTime, nullable=False)
116+
is_deleted: Mapped[int] = mapped_column(Boolean, default=False)
119117

120-
room: list[Room] = relationship(
118+
room: Mapped[list[Room]] = relationship(
121119
"Room",
122120
back_populates="events",
123121
secondary="events_rooms",
124122
secondaryjoin="and_(Room.id==EventsRooms.room_id, not_(Room.is_deleted))",
125123
)
126-
group: Group = relationship(
124+
group: Mapped[Group] = relationship(
127125
"Group",
128126
back_populates="events",
129127
foreign_keys="Event.group_id",
130128
primaryjoin="and_(Group.id==Event.group_id, not_(Group.is_deleted))",
131129
)
132-
lecturer: list[Lecturer] = relationship(
130+
lecturer: Mapped[list[Lecturer]] = relationship(
133131
"Lecturer",
134132
back_populates="events",
135133
secondary="events_lecturers",
136134
secondaryjoin="and_(Lecturer.id==EventsLecturers.lecturer_id, not_(Lecturer.is_deleted))",
137135
)
138-
comments: list[CommentEvent] = relationship(
136+
comments: Mapped[list[CommentEvent]] = relationship(
139137
"CommentEvent",
140138
foreign_keys="CommentEvent.event_id",
141139
back_populates="event",
@@ -144,22 +142,22 @@ class Event(BaseDbModel):
144142

145143

146144
class EventsLecturers(BaseDbModel):
147-
event_id = sqlalchemy.Column(sqlalchemy.Integer, sqlalchemy.ForeignKey("event.id"))
148-
lecturer_id = sqlalchemy.Column(sqlalchemy.Integer, sqlalchemy.ForeignKey("lecturer.id"))
145+
event_id: Mapped[int] = mapped_column(Integer, ForeignKey("event.id"))
146+
lecturer_id: Mapped[int] = mapped_column(Integer, ForeignKey("lecturer.id"))
149147

150148

151149
class EventsRooms(BaseDbModel):
152-
event_id = sqlalchemy.Column(sqlalchemy.Integer, sqlalchemy.ForeignKey("event.id"))
153-
room_id = sqlalchemy.Column(sqlalchemy.Integer, sqlalchemy.ForeignKey("room.id"))
150+
event_id: Mapped[int] = mapped_column(Integer, ForeignKey("event.id"))
151+
room_id: Mapped[int] = mapped_column(Integer, ForeignKey("room.id"))
154152

155153

156154
class Photo(BaseDbModel):
157-
lecturer_id = sqlalchemy.Column(sqlalchemy.Integer, sqlalchemy.ForeignKey("lecturer.id"))
158-
link = sqlalchemy.Column(sqlalchemy.String, unique=True)
159-
approve_status = sqlalchemy.Column(DbEnum(ApproveStatuses, native_enum=False), nullable=False)
160-
is_deleted = sqlalchemy.Column(sqlalchemy.Boolean, default=False)
155+
lecturer_id: Mapped[int] = mapped_column(Integer, ForeignKey("lecturer.id"))
156+
link: Mapped[int] = mapped_column(String, unique=True)
157+
approve_status: Mapped[int] = mapped_column(DbEnum(ApproveStatuses, native_enum=False), nullable=False)
158+
is_deleted: Mapped[int] = mapped_column(Boolean, default=False)
161159

162-
lecturer: Lecturer = relationship(
160+
lecturer: Mapped[Lecturer] = relationship(
163161
"Lecturer",
164162
back_populates="photos",
165163
foreign_keys="Photo.lecturer_id",
@@ -169,15 +167,15 @@ class Photo(BaseDbModel):
169167

170168

171169
class CommentLecturer(BaseDbModel):
172-
lecturer_id = sqlalchemy.Column(sqlalchemy.Integer, sqlalchemy.ForeignKey("lecturer.id"))
173-
author_name = sqlalchemy.Column(sqlalchemy.String, nullable=False)
174-
text = sqlalchemy.Column(sqlalchemy.String, nullable=False)
175-
approve_status = sqlalchemy.Column(DbEnum(ApproveStatuses, native_enum=False), nullable=False)
176-
create_ts = sqlalchemy.Column(sqlalchemy.DateTime, default=datetime.utcnow())
177-
update_ts = sqlalchemy.Column(sqlalchemy.DateTime, default=datetime.utcnow(), onupdate=datetime.utcnow())
178-
is_deleted = sqlalchemy.Column(sqlalchemy.Boolean, default=False)
179-
180-
lecturer: Lecturer = relationship(
170+
lecturer_id: Mapped[int] = mapped_column(Integer, ForeignKey("lecturer.id"))
171+
author_name: Mapped[int] = mapped_column(String, nullable=False)
172+
text: Mapped[int] = mapped_column(String, nullable=False)
173+
approve_status: Mapped[int] = mapped_column(DbEnum(ApproveStatuses, native_enum=False), nullable=False)
174+
create_ts: Mapped[int] = mapped_column(DateTime, default=datetime.utcnow())
175+
update_ts: Mapped[int] = mapped_column(DateTime, default=datetime.utcnow(), onupdate=datetime.utcnow())
176+
is_deleted: Mapped[int] = mapped_column(Boolean, default=False)
177+
178+
lecturer: Mapped[Lecturer] = relationship(
181179
"Lecturer",
182180
back_populates="comments",
183181
foreign_keys="CommentLecturer.lecturer_id",
@@ -186,15 +184,15 @@ class CommentLecturer(BaseDbModel):
186184

187185

188186
class CommentEvent(BaseDbModel):
189-
event_id = sqlalchemy.Column(sqlalchemy.Integer, sqlalchemy.ForeignKey("event.id"))
190-
author_name = sqlalchemy.Column(sqlalchemy.String, nullable=False)
191-
text = sqlalchemy.Column(sqlalchemy.String, nullable=False)
192-
approve_status = sqlalchemy.Column(DbEnum(ApproveStatuses, native_enum=False), nullable=False)
193-
create_ts = sqlalchemy.Column(sqlalchemy.DateTime, default=datetime.utcnow())
194-
update_ts = sqlalchemy.Column(sqlalchemy.DateTime, default=datetime.utcnow(), onupdate=datetime.utcnow())
195-
is_deleted = sqlalchemy.Column(sqlalchemy.Boolean, default=False)
196-
197-
event: Event = relationship(
187+
event_id: Mapped[int] = mapped_column(Integer, ForeignKey("event.id"))
188+
author_name: Mapped[int] = mapped_column(String, nullable=False)
189+
text: Mapped[int] = mapped_column(String, nullable=False)
190+
approve_status: Mapped[int] = mapped_column(DbEnum(ApproveStatuses, native_enum=False), nullable=False)
191+
create_ts: Mapped[int] = mapped_column(DateTime, default=datetime.utcnow())
192+
update_ts: Mapped[int] = mapped_column(DateTime, default=datetime.utcnow(), onupdate=datetime.utcnow())
193+
is_deleted: Mapped[int] = mapped_column(Boolean, default=False)
194+
195+
event: Mapped[Event] = relationship(
198196
"Event",
199197
back_populates="comments",
200198
foreign_keys="CommentEvent.event_id",

calendar_backend/routes/base.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -99,7 +99,6 @@ async def dispatch(self, request: Request, call_next: RequestResponseEndpoint) -
9999
app.add_middleware(
100100
DBSessionMiddleware,
101101
db_url=settings.DB_DSN,
102-
session_args={"autocommit": True},
103102
engine_args={"pool_pre_ping": True},
104103
)
105104
app.add_middleware(

calendar_backend/routes/event/comment.py

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,10 @@
2222
@event_comment_router.post("/comment/", response_model=CommentEventGet)
2323
async def comment_event(event_id: int, comment: EventCommentPost) -> CommentEventGet:
2424
approve_status = ApproveStatuses.APPROVED if not settings.REQUIRE_REVIEW_EVENT_COMMENT else ApproveStatuses.PENDING
25+
comment_event = DbCommentEvent.create(event_id=event_id, session=db.session, **comment.dict(), approve_status=approve_status)
26+
db.session.commit()
2527
return CommentEventGet.from_orm(
26-
DbCommentEvent.create(event_id=event_id, session=db.session, **comment.dict(), approve_status=approve_status)
28+
comment_event
2729
)
2830

2931

@@ -34,8 +36,10 @@ async def update_comment(id: int, event_id: int, comment_inp: EventCommentPatch)
3436
raise ObjectNotFound(DbCommentEvent, id)
3537
if comment.approve_status is not ApproveStatuses.PENDING:
3638
raise ForbiddenAction(DbCommentEvent, id)
39+
comment_event = DbCommentEvent.update(id, session=db.session, **comment_inp.dict(exclude_unset=True))
40+
db.session.commit()
3741
return CommentEventGet.from_orm(
38-
DbCommentEvent.update(id, session=db.session, **comment_inp.dict(exclude_unset=True))
42+
comment_event
3943
)
4044

4145

@@ -52,7 +56,9 @@ async def delete_comment(id: int, event_id: int, _: auth.User = Depends(auth.get
5256
comment = DbCommentEvent.get(id, only_approved=False, session=db.session)
5357
if comment.event_id != event_id or comment.approve_status != ApproveStatuses.APPROVED:
5458
raise ObjectNotFound(DbCommentEvent, id)
55-
return DbCommentEvent.delete(id=id, session=db.session)
59+
DbCommentEvent.delete(id=id, session=db.session)
60+
db.session.commit()
61+
return None
5662

5763

5864
@event_comment_router.get("/comment/", response_model=EventComments)

calendar_backend/routes/event/comment_review.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,5 +42,5 @@ async def review_comment(
4242
DbCommentEvent.update(comment.id, approve_status=action, session=db.session)
4343
if action == ApproveStatuses.DECLINED:
4444
DbCommentEvent.delete(comment.id, session=db.session)
45-
db.session.flush()
45+
db.session.commit()
4646
return CommentEventGet.from_orm(comment)

calendar_backend/routes/event/event.py

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -91,14 +91,16 @@ async def create_event(event: EventPost, _: auth.User = Depends(auth.get_current
9191
rooms = [Room.get(room_id, session=db.session) for room_id in event_dict.pop("room_id", [])]
9292
lecturers = [Lecturer.get(lecturer_id, session=db.session) for lecturer_id in event_dict.pop("lecturer_id", [])]
9393
group = Group.get(event.group_id, session=db.session)
94-
return EventGet.from_orm(
95-
Event.create(
94+
event_get = Event.create(
9695
**event_dict,
9796
room=rooms,
9897
lecturer=lecturers,
9998
group=group,
10099
session=db.session,
101100
)
101+
db.session.commit()
102+
return EventGet.from_orm(
103+
event_get
102104
)
103105

104106

@@ -119,21 +121,26 @@ async def create_events(events: list[EventPost], _: auth.User = Depends(auth.get
119121
session=db.session,
120122
)
121123
)
124+
db.session.commit()
122125
return parse_obj_as(list[EventGet], result)
123126

124127

125128
@event_router.patch("/{id}", response_model=EventGet)
126129
async def patch_event(id: int, event_inp: EventPatch, _: auth.User = Depends(auth.get_current_user)) -> EventGet:
127-
return EventGet.from_orm(Event.update(id, session=db.session, **event_inp.dict(exclude_unset=True)))
130+
patched = Event.update(id, session=db.session, **event_inp.dict(exclude_unset=True))
131+
db.session.commit()
132+
return EventGet.from_orm(patched)
128133

129134

130135
@event_router.delete("/bulk", response_model=None)
131136
async def delete_events(start: date, end: date, _: auth.User = Depends(auth.get_current_user)) -> None:
132137
db.session.query(Event).filter(Event.start_ts >= start, Event.end_ts < end).update(
133138
values={"is_deleted": True}
134139
)
140+
db.session.commit()
135141

136142

137143
@event_router.delete("/{id}", response_model=None)
138144
async def delete_event(id: int, _: auth.User = Depends(auth.get_current_user)) -> None:
139145
Event.delete(id, session=db.session)
146+
db.session.commit()

calendar_backend/routes/group/group.py

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,9 @@ async def get_groups(query: str = "", limit: int = 10, offset: int = 0) -> GetLi
3939
async def create_group(group: GroupPost, _: auth.User = Depends(auth.get_current_user)) -> GroupGet:
4040
if db.session.query(Group).filter(Group.number == group.number).one_or_none():
4141
raise HTTPException(status_code=423, detail="Already exists")
42-
return GroupGet.from_orm(Group.create(**group.dict(), session=db.session))
42+
group = Group.create(**group.dict(), session=db.session)
43+
db.session.commit()
44+
return GroupGet.from_orm(group)
4345

4446

4547
@group_router.patch("/{id}", response_model=GroupGet)
@@ -53,9 +55,12 @@ async def patch_group(
5355
query.id != id
5456
):
5557
raise HTTPException(status_code=423, detail="Already exists")
56-
return GroupGet.from_orm(Group.update(id, **group_inp.dict(exclude_unset=True), session=db.session))
58+
patched = Group.update(id, **group_inp.dict(exclude_unset=True), session=db.session)
59+
db.session.commit()
60+
return GroupGet.from_orm(patched)
5761

5862

5963
@group_router.delete("/{id}", response_model=None)
6064
async def delete_group(id: int, _: auth.User = Depends(auth.get_current_user)) -> None:
6165
Group.delete(id, session=db.session)
66+
db.session.commit()

0 commit comments

Comments
 (0)