Skip to content

Commit

Permalink
Fix backend lint errors
Browse files Browse the repository at this point in the history
  • Loading branch information
rwood-moz committed Jan 27, 2025
1 parent d8d611e commit 7e6e803
Show file tree
Hide file tree
Showing 35 changed files with 269 additions and 87 deletions.
2 changes: 1 addition & 1 deletion backend/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,6 @@ dependencies = { file = ["requirements.txt"] }
# Ruff
[tool.ruff]
line-length = 120

# Exclude a variety of commonly ignored directories.
exclude = [
".bzr",
Expand All @@ -57,6 +56,7 @@ exclude = [
"dist",
"node_modules",
"venv",
"src/appointment/migrations"
]

# Always generate Python 3.12-compatible code.
Expand Down
2 changes: 1 addition & 1 deletion backend/src/appointment/controller/apis/fxa_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
from requests_oauthlib import OAuth2Session
import requests
from ...database import models, repo
from ...exceptions.fxa_api import NotInAllowListException, MissingRefreshTokenException
from ...exceptions.fxa_api import MissingRefreshTokenException


class FxaConfig:
Expand Down
6 changes: 5 additions & 1 deletion backend/src/appointment/controller/apis/google_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,11 @@ def get_free_busy(self, calendar_ids, time_min, time_max, token):
perf_start = time.perf_counter_ns()
with build('calendar', 'v3', credentials=token, cache_discovery=False) as service:
request = service.freebusy().query(
body=dict(timeMin=time_min, timeMax=time_max, items=[{'id': calendar_id} for calendar_id in calendar_ids])
body=dict(
timeMin=time_min,
timeMax=time_max,
items=[{'id': calendar_id} for calendar_id in calendar_ids]
)
)

while request is not None:
Expand Down
5 changes: 4 additions & 1 deletion backend/src/appointment/controller/auth.py
Original file line number Diff line number Diff line change
Expand Up @@ -73,4 +73,7 @@ def schedule_links_by_subscriber(db, subscriber: models.Subscriber):
url_safe_username = urllib.parse.quote_plus(subscriber.username)

# Empty space at join is for trailing slash!
return list(map(lambda sch: '/'.join([short_url, url_safe_username, urllib.parse.quote_plus(sch.slug), '']), schedules))
return list(map(
lambda sch: '/'.join([short_url, url_safe_username, urllib.parse.quote_plus(sch.slug), '']),
schedules)
)
38 changes: 29 additions & 9 deletions backend/src/appointment/controller/calendar.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@
import time
import zoneinfo
import os
from socket import getaddrinfo
from urllib.parse import urlparse, urljoin

import caldav.lib.error
Expand All @@ -27,8 +26,7 @@
from sqlalchemy.orm import Session

from .. import utils
from ..database.schemas import CalendarConnection
from ..defines import REDIS_REMOTE_EVENTS_KEY, DATEFMT, DEFAULT_CALENDAR_COLOUR, DATETIMEFMT
from ..defines import REDIS_REMOTE_EVENTS_KEY, DATEFMT, DEFAULT_CALENDAR_COLOUR
from .apis.google_client import GoogleClient
from ..database.models import CalendarProvider, BookingStatus
from ..database import schemas, models, repo
Expand Down Expand Up @@ -82,7 +80,12 @@ def get_cached_events(self, key_scope):

return [schemas.Event.model_load_redis(blob) for blob in json.loads(encrypted_events)]

def put_cached_events(self, key_scope, events: list[schemas.Event], expiry=os.getenv('REDIS_EVENT_EXPIRE_SECONDS', 900)):
def put_cached_events(
self,
key_scope,
events: list[schemas.Event],
expiry=os.getenv('REDIS_EVENT_EXPIRE_SECONDS', 900)
):
"""Sets the passed cached events with an option to set a custom expiry time."""
if self.redis_instance is None:
return False
Expand Down Expand Up @@ -291,7 +294,11 @@ def save_event(
},
}

new_event = self.google_client.save_event(calendar_id=self.remote_calendar_id, body=body, token=self.google_token)
new_event = self.google_client.save_event(
calendar_id=self.remote_calendar_id,
body=body,
token=self.google_token
)

# Fill in the external_id so we can delete events later!
event.external_id = new_event.get('id')
Expand All @@ -314,7 +321,16 @@ def delete_events(self, start):


class CalDavConnector(BaseConnector):
def __init__(self, db: Session, subscriber_id: int, calendar_id: int, redis_instance, url: str, user: str, password: str):
def __init__(
self,
db: Session,
subscriber_id: int,
calendar_id: int,
redis_instance,
url: str,
user: str,
password: str
):
super().__init__(subscriber_id, calendar_id, redis_instance)

self.db = db
Expand Down Expand Up @@ -343,8 +359,8 @@ def get_busy_time(self, calendar_ids: list, start: str, end: str):

items = []

# This is sort of dumb, freebusy object isn't exposed in the icalendar instance except through a list of tuple props
# Luckily the value is a vPeriod which is a tuple of date times/timedelta (0 = Start, 1 = End)
# This is sort of dumb, freebusy object isn't exposed in the icalendar instance except through a list of tuple
# props; luckily the value is a vPeriod which is a tuple of date times/timedelta (0 = Start, 1 = End)
for prop in response.icalendar_instance.property_items():
if prop[0].lower() != 'freebusy':
continue
Expand Down Expand Up @@ -904,7 +920,11 @@ def existing_events_for_schedule(
start=busy.get('start'),
end=busy.get('end'),
title='Busy'
) for busy in con.get_busy_time([calendar.user for calendar in google_calendars], start.strftime(DATEFMT), end.strftime(DATEFMT))
) for busy in con.get_busy_time(
[calendar.user for calendar in google_calendars],
start.strftime(DATEFMT),
end.strftime(DATEFMT)
)
])

# handle already requested time slots
Expand Down
17 changes: 15 additions & 2 deletions backend/src/appointment/controller/mailer.py
Original file line number Diff line number Diff line change
Expand Up @@ -352,7 +352,15 @@ def __init__(self, name, email, date, duration, schedule_name, *args, **kwargs):
self.schedule_name = schedule_name
lang = kwargs['lang'] if 'lang' in kwargs else None
default_kwargs = {'subject': l10n('new-booking-subject', {'name': name}, lang)}
super(NewBookingMail, self).__init__(name=name, email=email, date=date, duration=duration, *args, **default_kwargs, **kwargs)
super(NewBookingMail, self).__init__(
name=name,
email=email,
date=date,
duration=duration,
*args,
**default_kwargs,
**kwargs
)
self.reply_to = email

def text(self):
Expand Down Expand Up @@ -391,7 +399,12 @@ def __init__(self, requestee_name, requestee_email, topic, details, *args, **kwa
self.topic = topic
self.details = details
default_kwargs = {'subject': l10n('support-mail-subject', {'topic': topic})}
super(SupportRequestMail, self).__init__(os.getenv('SUPPORT_EMAIL', '[email protected]'), *args, **default_kwargs, **kwargs)
super(SupportRequestMail, self).__init__(
os.getenv('SUPPORT_EMAIL', '[email protected]'),
*args,
**default_kwargs,
**kwargs
)
self.reply_to = requestee_email

def text(self):
Expand Down
48 changes: 41 additions & 7 deletions backend/src/appointment/database/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -164,8 +164,20 @@ class Subscriber(HasSoftDelete, Base):
external_connections = relationship('ExternalConnections', cascade='all,delete', back_populates='owner')

# FIXME: Invite will be deleted if either the owner or the invited subscriber is deleted.
invite: Mapped['Invite'] = relationship('Invite', cascade='all,delete', back_populates='subscriber', uselist=False, foreign_keys='Invite.subscriber_id')
owned_invites: Mapped[list['Invite']] = relationship('Invite', cascade='all,delete', back_populates='owner', foreign_keys='[Invite.owner_id]')
invite: Mapped['Invite'] = relationship(
'Invite',
cascade='all,delete',
back_populates='subscriber',
uselist=False,
foreign_keys='Invite.subscriber_id'
)

owned_invites: Mapped[list['Invite']] = relationship(
'Invite',
cascade='all,delete',
back_populates='owner',
foreign_keys='[Invite.owner_id]'
)

def get_external_connection(self, type: ExternalConnectionType) -> 'ExternalConnections':
"""Retrieves the first found external connection by type or returns None if not found"""
Expand Down Expand Up @@ -242,7 +254,12 @@ class Appointment(Base):
)

calendar: Mapped[Calendar] = relationship('Calendar', back_populates='appointments')
slots: Mapped[list['Slot']] = relationship('Slot', cascade='all,delete', back_populates='appointment', lazy='joined')
slots: Mapped[list['Slot']] = relationship(
'Slot',
cascade='all,delete',
back_populates='appointment',
lazy='joined'
)


class Attendee(Base):
Expand Down Expand Up @@ -305,7 +322,7 @@ class Schedule(Base):
weekdays: str | dict = Column(JSON, default='[1,2,3,4,5]') # list of ISO weekdays, Mo-Su => 1-7
slot_duration: int = Column(Integer, default=30) # defaults to 30 minutes
booking_confirmation: bool = Column(Boolean, index=True, nullable=False, default=True)
timezone: str = Column(encrypted_type(String), index=True, nullable=True) # Not used right now but will be in the future
timezone: str = Column(encrypted_type(String), index=True, nullable=True) # Not used now but will be in the future

# What (if any) meeting link will we generate once the meeting is booked
meeting_link_provider: MeetingLinkProviderType = Column(
Expand Down Expand Up @@ -385,9 +402,26 @@ class Invite(Base):
code = Column(encrypted_type(String), index=False)
status = Column(Enum(InviteStatus), index=True)

owner: Mapped['Subscriber'] = relationship('Subscriber', back_populates='invite', single_parent=True, foreign_keys=[owner_id])
subscriber: Mapped['Subscriber'] = relationship('Subscriber', back_populates='invite', single_parent=True, foreign_keys=[subscriber_id])
waiting_list: Mapped['WaitingList'] = relationship('WaitingList', cascade='all,delete', back_populates='invite', uselist=False)
owner: Mapped['Subscriber'] = relationship(
'Subscriber',
back_populates='invite',
single_parent=True,
foreign_keys=[owner_id]
)

subscriber: Mapped['Subscriber'] = relationship(
'Subscriber',
back_populates='invite',
single_parent=True,
foreign_keys=[subscriber_id]
)

waiting_list: Mapped['WaitingList'] = relationship(
'WaitingList',
cascade='all,delete',
back_populates='invite',
uselist=False
)

@property
def is_used(self) -> bool:
Expand Down
13 changes: 11 additions & 2 deletions backend/src/appointment/database/repo/calendar.py
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,11 @@ def create(db: Session, calendar: schemas.CalendarConnection, subscriber_id: int
return db_calendar


def update_by_calendar(db: Session, calendar: schemas.CalendarConnection, db_calendar: models.Calendar) -> models.Calendar|None:
def update_by_calendar(
db: Session,
calendar: schemas.CalendarConnection,
db_calendar: models.Calendar
) -> models.Calendar|None:
"""Update a calendar from the database with calendar data."""

# list of all attributes that must never be updated
Expand Down Expand Up @@ -142,7 +146,12 @@ def delete_by_subscriber(db: Session, subscriber_id: int):
return True


def delete_by_subscriber_and_provider(db: Session, subscriber_id: int, provider: models.CalendarProvider, user: Optional[str] = None):
def delete_by_subscriber_and_provider(
db: Session,
subscriber_id: int,
provider: models.CalendarProvider,
user: Optional[str] = None
):
"""Delete all subscriber's calendar by a provider"""
calendars = get_by_subscriber(db, subscriber_id=subscriber_id)
for calendar in calendars:
Expand Down
7 changes: 6 additions & 1 deletion backend/src/appointment/database/repo/invite.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,12 @@ def get_by_subscriber(db: Session, subscriber_id: int) -> models.Invite:
return db.query(models.Invite).filter(models.Invite.subscriber_id == subscriber_id).first()


def get_by_owner(db: Session, subscriber_id: int, status: Optional[InviteStatus] = None, only_unused: bool = False) -> list[models.Invite]:
def get_by_owner(
db: Session,
subscriber_id: int,
status: Optional[InviteStatus] = None,
only_unused: bool = False
) -> list[models.Invite]:
"""Retrieve invites by the invite owner. Optionally filter by status, or unused."""
query = db.query(models.Invite)
filters = [models.Invite.owner_id == subscriber_id]
Expand Down
2 changes: 0 additions & 2 deletions backend/src/appointment/database/repo/subscriber.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,8 @@
Repository providing CRUD functions for subscriber database models.
"""

import re
import datetime
import secrets
import urllib.parse

from sqlalchemy.orm import Session
from .. import models, schemas
Expand Down
13 changes: 11 additions & 2 deletions backend/src/appointment/database/schemas.py
Original file line number Diff line number Diff line change
Expand Up @@ -211,8 +211,17 @@ def start_time_should_be_before_end_time(self) -> Self:
# Fallback to utc...
tz = self.timezone or 'UTC'

start_time = datetime.combine(self.start_date, self.start_time, tzinfo=timezone.utc).astimezone(zoneinfo.ZoneInfo(tz))
end_time = datetime.combine(self.start_date, self.end_time, tzinfo=timezone.utc).astimezone(zoneinfo.ZoneInfo(tz))
start_time = datetime.combine(
self.start_date,
self.start_time,
tzinfo=timezone.utc).astimezone(zoneinfo.ZoneInfo(tz)
)

end_time = datetime.combine(
self.start_date,
self.end_time,
tzinfo=timezone.utc).astimezone(zoneinfo.ZoneInfo(tz)
)

start_time = (start_time + timedelta(minutes=self.slot_duration))
end_time = end_time
Expand Down
6 changes: 4 additions & 2 deletions backend/src/appointment/dependencies/database.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,8 @@ def boot_redis_cluster():
host = os.getenv('REDIS_URL')
port = int(os.getenv('REDIS_PORT'))
password = os.getenv('REDIS_PASSWORD')
ssl = True if os.getenv('REDIS_USE_SSL') and (os.getenv('REDIS_USE_SSL').lower() == 'true' or os.getenv('REDIS_USE_SSL').lower() == '1') else False
ssl = True if os.getenv('REDIS_USE_SSL') and (os.getenv('REDIS_USE_SSL').lower() == 'true' or \
os.getenv('REDIS_USE_SSL').lower() == '1') else False
timer_boot = time.perf_counter_ns()

# Retry strategy
Expand Down Expand Up @@ -86,7 +87,8 @@ def get_redis() -> Redis | RedisCluster | None:
port = int(os.getenv('REDIS_PORT'))
db = os.getenv('REDIS_DB')
password = os.getenv('REDIS_PASSWORD')
ssl = True if os.getenv('REDIS_USE_SSL') and (os.getenv('REDIS_USE_SSL').lower() == 'true' or os.getenv('REDIS_USE_SSL').lower() == '1') else False
ssl = True if os.getenv('REDIS_USE_SSL') and (os.getenv('REDIS_USE_SSL').lower() == 'true' or \
os.getenv('REDIS_USE_SSL').lower() == '1') else False

timer_boot = time.perf_counter_ns()

Expand Down
6 changes: 5 additions & 1 deletion backend/src/appointment/routes/account.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,11 @@ def download_data(db: Session = Depends(get_db), subscriber: Subscriber = Depend


@router.delete('/delete')
def delete_account(db: Session = Depends(get_db), subscriber: Subscriber = Depends(get_subscriber), posthog: Posthog = Depends(get_posthog)):
def delete_account(
db: Session = Depends(get_db),
subscriber: Subscriber = Depends(get_subscriber),
posthog: Posthog = Depends(get_posthog)
):
"""Delete your account and all the data associated with it forever!"""
try:
return data.delete_account(db, subscriber)
Expand Down
14 changes: 3 additions & 11 deletions backend/src/appointment/routes/api.py
Original file line number Diff line number Diff line change
@@ -1,32 +1,24 @@
import datetime
import json
import logging
import os
import secrets
import uuid
from urllib.parse import urlparse

import requests.exceptions
import sentry_sdk
import tzlocal
from sentry_sdk import metrics
from redis import Redis, RedisCluster

# database
from sqlalchemy.orm import Session
from starlette.responses import HTMLResponse, JSONResponse
from starlette.responses import JSONResponse

from .. import utils
from ..controller.mailer import Attachment
from ..database import repo, schemas

# authentication
from ..controller.calendar import CalDavConnector, Tools, GoogleConnector
from fastapi import APIRouter, Depends, HTTPException, Body, BackgroundTasks, Request
from fastapi import APIRouter, Depends, HTTPException, BackgroundTasks, Request
from ..controller.apis.google_client import GoogleClient
from ..controller.auth import signed_url_by_subscriber, schedule_links_by_subscriber
from ..database.models import Subscriber, CalendarProvider, MeetingLinkProviderType, ExternalConnectionType, \
InviteStatus
from ..database.models import Subscriber, CalendarProvider, InviteStatus
from ..defines import DEFAULT_CALENDAR_COLOUR
from ..dependencies.google import get_google_client
from ..dependencies.auth import get_subscriber
Expand Down
Loading

0 comments on commit 7e6e803

Please sign in to comment.