diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 2da44625..6b8e861f 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -7,18 +7,19 @@ repos: hooks: - id: check-yaml - id: trailing-whitespace - - repo: https://github.com/psf/black - rev: 22.3.0 - hooks: - - id: black - - repo: https://github.com/pycqa/flake8 - rev: 3.9.2 - hooks: - - id: flake8 - repo: https://github.com/Riverside-Healthcare/djLint rev: v1.3.0 hooks: - id: djlint-jinja files: '.*\.html' entry: djlint --reformat + - repo: https://github.com/astral-sh/ruff-pre-commit + # Ruff version. + rev: v0.1.5 + hooks: + # Run the linter. + - id: ruff + args: [ --fix ] + # Run the formatter. + - id: ruff-format diff --git a/app/account_linking.py b/app/account_linking.py index ea0451de..967b6c65 100644 --- a/app/account_linking.py +++ b/app/account_linking.py @@ -168,7 +168,6 @@ class NewUserStrategy(ClientMergeStrategy): class ExistingUnlinkedUserStrategy(ClientMergeStrategy): def process(self) -> LinkResult: - partner_user = ensure_partner_user_exists_for_user( self.link_request, self.user, self.partner ) diff --git a/app/alias_suffix.py b/app/alias_suffix.py index 6a2c69e8..8cfc98aa 100644 --- a/app/alias_suffix.py +++ b/app/alias_suffix.py @@ -70,7 +70,6 @@ def verify_prefix_suffix( # when DISABLE_ALIAS_SUFFIX is true, alias_domain_prefix is empty and not config.DISABLE_ALIAS_SUFFIX ): - if not alias_domain_prefix.startswith("."): LOG.e("User %s submits a wrong alias suffix %s", user, alias_suffix) return False diff --git a/app/api/__init__.py b/app/api/__init__.py index 6ff42d24..f9341c0a 100644 --- a/app/api/__init__.py +++ b/app/api/__init__.py @@ -16,3 +16,22 @@ from .views import ( sudo, user, ) + +__all__ = [ + "alias_options", + "new_custom_alias", + "custom_domain", + "new_random_alias", + "user_info", + "auth", + "auth_mfa", + "alias", + "apple", + "mailbox", + "notification", + "setting", + "export", + "phone", + "sudo", + "user", +] diff --git a/app/api/views/mailbox.py b/app/api/views/mailbox.py index 2d8fa83c..1c2c0f92 100644 --- a/app/api/views/mailbox.py +++ b/app/api/views/mailbox.py @@ -45,7 +45,7 @@ def create_mailbox(): mailbox_email = sanitize_email(request.get_json().get("email")) if not user.is_premium(): - return jsonify(error=f"Only premium plan can add additional mailbox"), 400 + return jsonify(error="Only premium plan can add additional mailbox"), 400 if not is_valid_email(mailbox_email): return jsonify(error=f"{mailbox_email} invalid"), 400 diff --git a/app/api/views/new_custom_alias.py b/app/api/views/new_custom_alias.py index 88d41917..0e21815f 100644 --- a/app/api/views/new_custom_alias.py +++ b/app/api/views/new_custom_alias.py @@ -150,7 +150,7 @@ def new_custom_alias_v3(): if not data: return jsonify(error="request body cannot be empty"), 400 - if type(data) is not dict: + if not isinstance(data, dict): return jsonify(error="request body does not follow the required format"), 400 alias_prefix = data.get("alias_prefix", "").strip().lower().replace(" ", "") @@ -168,7 +168,7 @@ def new_custom_alias_v3(): return jsonify(error="alias prefix invalid format or too long"), 400 # check if mailbox is not tempered with - if type(mailbox_ids) is not list: + if not isinstance(mailbox_ids, list): return jsonify(error="mailbox_ids must be an array of id"), 400 mailboxes = [] for mailbox_id in mailbox_ids: diff --git a/app/auth/__init__.py b/app/auth/__init__.py index 61fe17ed..e8adcc6c 100644 --- a/app/auth/__init__.py +++ b/app/auth/__init__.py @@ -17,3 +17,23 @@ from .views import ( recovery, api_to_cookie, ) + +__all__ = [ + "login", + "logout", + "register", + "activate", + "resend_activation", + "reset_password", + "forgot_password", + "github", + "google", + "facebook", + "proton", + "change_email", + "mfa", + "fido", + "social", + "recovery", + "api_to_cookie", +] diff --git a/app/auth/views/fido.py b/app/auth/views/fido.py index 445fd83f..5f1635d9 100644 --- a/app/auth/views/fido.py +++ b/app/auth/views/fido.py @@ -62,7 +62,7 @@ def fido(): browser = MfaBrowser.get_by(token=request.cookies.get("mfa")) if browser and not browser.is_expired() and browser.user_id == user.id: login_user(user) - flash(f"Welcome back!", "success") + flash("Welcome back!", "success") # Redirect user to correct page return redirect(next_url or url_for("dashboard.index")) else: @@ -110,7 +110,7 @@ def fido(): session["sudo_time"] = int(time()) login_user(user) - flash(f"Welcome back!", "success") + flash("Welcome back!", "success") # Redirect user to correct page response = make_response(redirect(next_url or url_for("dashboard.index"))) diff --git a/app/auth/views/mfa.py b/app/auth/views/mfa.py index 80dcb0bf..bc7680b1 100644 --- a/app/auth/views/mfa.py +++ b/app/auth/views/mfa.py @@ -55,7 +55,7 @@ def mfa(): browser = MfaBrowser.get_by(token=request.cookies.get("mfa")) if browser and not browser.is_expired() and browser.user_id == user.id: login_user(user) - flash(f"Welcome back!", "success") + flash("Welcome back!", "success") # Redirect user to correct page return redirect(next_url or url_for("dashboard.index")) else: @@ -73,7 +73,7 @@ def mfa(): Session.commit() login_user(user) - flash(f"Welcome back!", "success") + flash("Welcome back!", "success") # Redirect user to correct page response = make_response(redirect(next_url or url_for("dashboard.index"))) diff --git a/app/auth/views/recovery.py b/app/auth/views/recovery.py index 6c3021a0..97659779 100644 --- a/app/auth/views/recovery.py +++ b/app/auth/views/recovery.py @@ -53,7 +53,7 @@ def recovery_route(): del session[MFA_USER_ID] login_user(user) - flash(f"Welcome back!", "success") + flash("Welcome back!", "success") recovery_code.used = True recovery_code.used_at = arrow.now() diff --git a/app/auth/views/register.py b/app/auth/views/register.py index 138a9719..f40a98a2 100644 --- a/app/auth/views/register.py +++ b/app/auth/views/register.py @@ -94,9 +94,7 @@ def register(): try: send_activation_email(user, next_url) RegisterEvent(RegisterEvent.ActionType.success).send() - DailyMetric.get_or_create_today_metric().nb_new_web_non_proton_user += ( - 1 - ) + DailyMetric.get_or_create_today_metric().nb_new_web_non_proton_user += 1 Session.commit() except Exception: flash("Invalid email, are you sure the email is correct?", "error") diff --git a/app/dashboard/__init__.py b/app/dashboard/__init__.py index ebfc38d6..2a0c5c14 100644 --- a/app/dashboard/__init__.py +++ b/app/dashboard/__init__.py @@ -33,3 +33,39 @@ from .views import ( notification, support, ) + +__all__ = [ + "index", + "pricing", + "setting", + "custom_alias", + "subdomain", + "billing", + "alias_log", + "alias_export", + "unsubscribe", + "api_key", + "custom_domain", + "alias_contact_manager", + "enter_sudo", + "mfa_setup", + "mfa_cancel", + "fido_setup", + "coupon", + "fido_manage", + "domain_detail", + "lifetime_licence", + "directory", + "mailbox", + "mailbox_detail", + "refused_email", + "referral", + "contact_detail", + "setup_done", + "batch_import", + "alias_transfer", + "app", + "delete_account", + "notification", + "support", +] diff --git a/app/dashboard/views/alias_log.py b/app/dashboard/views/alias_log.py index 06708446..278aaf5a 100644 --- a/app/dashboard/views/alias_log.py +++ b/app/dashboard/views/alias_log.py @@ -87,6 +87,6 @@ def get_alias_log(alias: Alias, page_id=0) -> [AliasLog]: contact=contact, ) logs.append(al) - logs = sorted(logs, key=lambda l: l.when, reverse=True) + logs = sorted(logs, key=lambda log: log.when, reverse=True) return logs diff --git a/app/dashboard/views/app.py b/app/dashboard/views/app.py index 6d4f913a..bf9f5a3b 100644 --- a/app/dashboard/views/app.py +++ b/app/dashboard/views/app.py @@ -1,14 +1,9 @@ -from app.db import Session - -""" -List of apps that user has used via the "Sign in with SimpleLogin" -""" - from flask import render_template, request, flash, redirect from flask_login import login_required, current_user from sqlalchemy.orm import joinedload from app.dashboard.base import dashboard_bp +from app.db import Session from app.models import ( ClientUser, ) @@ -17,6 +12,10 @@ from app.models import ( @dashboard_bp.route("/app", methods=["GET", "POST"]) @login_required def app_route(): + """ + List of apps that user has used via the "Sign in with SimpleLogin" + """ + client_users = ( ClientUser.filter_by(user_id=current_user.id) .options(joinedload(ClientUser.client)) diff --git a/app/dashboard/views/coupon.py b/app/dashboard/views/coupon.py index f9918870..e182837b 100644 --- a/app/dashboard/views/coupon.py +++ b/app/dashboard/views/coupon.py @@ -100,7 +100,7 @@ def coupon_route(): commit=True, ) flash( - f"Your account has been upgraded to Premium, thanks for your support!", + "Your account has been upgraded to Premium, thanks for your support!", "success", ) diff --git a/app/dashboard/views/directory.py b/app/dashboard/views/directory.py index e32f8ec7..668ed013 100644 --- a/app/dashboard/views/directory.py +++ b/app/dashboard/views/directory.py @@ -67,7 +67,7 @@ def directory(): if request.method == "POST": if request.form.get("form-name") == "delete": if not delete_dir_form.validate(): - flash(f"Invalid request", "warning") + flash("Invalid request", "warning") return redirect(url_for("dashboard.directory")) dir_obj = Directory.get(delete_dir_form.directory_id.data) @@ -87,7 +87,7 @@ def directory(): if request.form.get("form-name") == "toggle-directory": if not toggle_dir_form.validate(): - flash(f"Invalid request", "warning") + flash("Invalid request", "warning") return redirect(url_for("dashboard.directory")) dir_id = toggle_dir_form.directory_id.data dir_obj = Directory.get(dir_id) @@ -109,7 +109,7 @@ def directory(): elif request.form.get("form-name") == "update": if not update_dir_form.validate(): - flash(f"Invalid request", "warning") + flash("Invalid request", "warning") return redirect(url_for("dashboard.directory")) dir_id = update_dir_form.directory_id.data dir_obj = Directory.get(dir_id) diff --git a/app/dashboard/views/setting.py b/app/dashboard/views/setting.py index 832ce839..f177ee43 100644 --- a/app/dashboard/views/setting.py +++ b/app/dashboard/views/setting.py @@ -128,7 +128,6 @@ def setting(): new_email_valid = True new_email = canonicalize_email(change_email_form.email.data) if new_email != current_user.email and not pending_email: - # check if this email is not already used if personal_email_already_used(new_email) or Alias.get_by( email=new_email diff --git a/app/dashboard/views/unsubscribe.py b/app/dashboard/views/unsubscribe.py index 18ff7e3b..f3287441 100644 --- a/app/dashboard/views/unsubscribe.py +++ b/app/dashboard/views/unsubscribe.py @@ -75,12 +75,11 @@ def block_contact(contact_id): @dashboard_bp.route("/unsubscribe/encoded/", methods=["GET"]) @login_required def encoded_unsubscribe(encoded_request: str): - unsub_data = UnsubscribeHandler().handle_unsubscribe_from_request( current_user, encoded_request ) if not unsub_data: - flash(f"Invalid unsubscribe request", "error") + flash("Invalid unsubscribe request", "error") return redirect(url_for("dashboard.index")) if unsub_data.action == UnsubscribeAction.DisableAlias: alias = Alias.get(unsub_data.data) @@ -97,14 +96,14 @@ def encoded_unsubscribe(encoded_request: str): ) ) if unsub_data.action == UnsubscribeAction.UnsubscribeNewsletter: - flash(f"You've unsubscribed from the newsletter", "success") + flash("You've unsubscribed from the newsletter", "success") return redirect( url_for( "dashboard.index", ) ) if unsub_data.action == UnsubscribeAction.OriginalUnsubscribeMailto: - flash(f"The original unsubscribe request has been forwarded", "success") + flash("The original unsubscribe request has been forwarded", "success") return redirect( url_for( "dashboard.index", diff --git a/app/developer/__init__.py b/app/developer/__init__.py index 0a204cef..0f5f4688 100644 --- a/app/developer/__init__.py +++ b/app/developer/__init__.py @@ -1 +1,3 @@ from .views import index, new_client, client_detail + +__all__ = ["index", "new_client", "client_detail"] diff --git a/app/developer/views/client_detail.py b/app/developer/views/client_detail.py index aa625270..26d08f85 100644 --- a/app/developer/views/client_detail.py +++ b/app/developer/views/client_detail.py @@ -87,7 +87,7 @@ def client_detail(client_id): ) flash( - f"Thanks for submitting, we are informed and will come back to you asap!", + "Thanks for submitting, we are informed and will come back to you asap!", "success", ) diff --git a/app/discover/__init__.py b/app/discover/__init__.py index 86bd305b..78a3e976 100644 --- a/app/discover/__init__.py +++ b/app/discover/__init__.py @@ -1 +1,3 @@ from .views import index + +__all__ = ["index"] diff --git a/app/email_utils.py b/app/email_utils.py index 67e406ff..75e47bbc 100644 --- a/app/email_utils.py +++ b/app/email_utils.py @@ -93,7 +93,7 @@ def send_welcome_email(user): send_email( comm_email, - f"Welcome to SimpleLogin", + "Welcome to SimpleLogin", render("com/welcome.txt", user=user, alias=alias), render("com/welcome.html", user=user, alias=alias), unsubscribe_link, @@ -104,7 +104,7 @@ def send_welcome_email(user): def send_trial_end_soon_email(user): send_email( user.email, - f"Your trial will end soon", + "Your trial will end soon", render("transactional/trial-end.txt.jinja2", user=user), render("transactional/trial-end.html", user=user), ignore_smtp_error=True, @@ -114,7 +114,7 @@ def send_trial_end_soon_email(user): def send_activation_email(email, activation_link): send_email( email, - f"Just one more step to join SimpleLogin", + "Just one more step to join SimpleLogin", render( "transactional/activation.txt", activation_link=activation_link, @@ -768,7 +768,7 @@ def get_header_unicode(header: Union[str, Header]) -> str: ret = "" for to_decoded_str, charset in decode_header(header): if charset is None: - if type(to_decoded_str) is bytes: + if isinstance(to_decoded_str, bytes): decoded_str = to_decoded_str.decode() else: decoded_str = to_decoded_str @@ -805,13 +805,13 @@ def to_bytes(msg: Message): for generator_policy in [None, policy.SMTP, policy.SMTPUTF8]: try: return msg.as_bytes(policy=generator_policy) - except: + except Exception: LOG.w("as_bytes() fails with %s policy", policy, exc_info=True) msg_string = msg.as_string() try: return msg_string.encode() - except: + except Exception: LOG.w("as_string().encode() fails", exc_info=True) return msg_string.encode(errors="replace") @@ -906,7 +906,7 @@ def add_header(msg: Message, text_header, html_header=None) -> Message: if content_type == "text/plain": encoding = get_encoding(msg) payload = msg.get_payload() - if type(payload) is str: + if isinstance(payload, str): clone_msg = copy(msg) new_payload = f"""{text_header} ------------------------------ @@ -916,7 +916,7 @@ def add_header(msg: Message, text_header, html_header=None) -> Message: elif content_type == "text/html": encoding = get_encoding(msg) payload = msg.get_payload() - if type(payload) is str: + if isinstance(payload, str): new_payload = f""" @@ -972,7 +972,7 @@ def add_header(msg: Message, text_header, html_header=None) -> Message: def replace(msg: Union[Message, str], old, new) -> Union[Message, str]: - if type(msg) is str: + if isinstance(msg, str): msg = msg.replace(old, new) return msg @@ -995,7 +995,7 @@ def replace(msg: Union[Message, str], old, new) -> Union[Message, str]: if content_type in ("text/plain", "text/html"): encoding = get_encoding(msg) payload = msg.get_payload() - if type(payload) is str: + if isinstance(payload, str): if encoding == EmailEncoding.QUOTED: LOG.d("handle quoted-printable replace %s -> %s", old, new) # first decode the payload diff --git a/app/handler/dmarc.py b/app/handler/dmarc.py index 73c1fb60..05465f38 100644 --- a/app/handler/dmarc.py +++ b/app/handler/dmarc.py @@ -34,10 +34,10 @@ def apply_dmarc_policy_for_forward_phase( from_header = get_header_unicode(msg[headers.FROM]) - warning_plain_text = f"""This email failed anti-phishing checks when it was received by SimpleLogin, be careful with its content. + warning_plain_text = """This email failed anti-phishing checks when it was received by SimpleLogin, be careful with its content. More info on https://simplelogin.io/docs/getting-started/anti-phishing/ """ - warning_html = f""" + warning_html = """

This email failed anti-phishing checks when it was received by SimpleLogin, be careful with its content. More info on anti-phishing measure diff --git a/app/handler/provider_complaint.py b/app/handler/provider_complaint.py index 64af81cc..3a10ef78 100644 --- a/app/handler/provider_complaint.py +++ b/app/handler/provider_complaint.py @@ -221,7 +221,7 @@ def handle_complaint(message: Message, origin: ProviderComplaintOrigin) -> bool: return True if is_deleted_alias(msg_info.sender_address): - LOG.i(f"Complaint is for deleted alias. Do nothing") + LOG.i("Complaint is for deleted alias. Do nothing") return True contact = Contact.get_by(reply_email=msg_info.sender_address) @@ -231,7 +231,7 @@ def handle_complaint(message: Message, origin: ProviderComplaintOrigin) -> bool: alias = find_alias_with_address(msg_info.rcpt_address) if is_deleted_alias(msg_info.rcpt_address): - LOG.i(f"Complaint is for deleted alias. Do nothing") + LOG.i("Complaint is for deleted alias. Do nothing") return True if not alias: diff --git a/app/handler/unsubscribe_encoder.py b/app/handler/unsubscribe_encoder.py index 1f878b4e..5a497c3d 100644 --- a/app/handler/unsubscribe_encoder.py +++ b/app/handler/unsubscribe_encoder.py @@ -54,9 +54,8 @@ class UnsubscribeEncoder: def encode_subject( cls, action: UnsubscribeAction, data: Union[int, UnsubscribeOriginalData] ) -> str: - if ( - action != UnsubscribeAction.OriginalUnsubscribeMailto - and type(data) is not int + if action != UnsubscribeAction.OriginalUnsubscribeMailto and not isinstance( + data, int ): raise ValueError(f"Data has to be an int for an action of type {action}") if action == UnsubscribeAction.OriginalUnsubscribeMailto: diff --git a/app/import_utils.py b/app/import_utils.py index 0f5421ac..9d4460e3 100644 --- a/app/import_utils.py +++ b/app/import_utils.py @@ -30,7 +30,7 @@ def handle_batch_import(batch_import: BatchImport): LOG.d("Download file %s from %s", batch_import.file, file_url) r = requests.get(file_url) - lines = [line.decode() for line in r.iter_lines()] + lines = [line.decode("utf-8") for line in r.iter_lines()] import_from_csv(batch_import, user, lines) diff --git a/app/internal/__init__.py b/app/internal/__init__.py index c92d4670..0408fd31 100644 --- a/app/internal/__init__.py +++ b/app/internal/__init__.py @@ -1,2 +1,4 @@ from .integrations import set_enable_proton_cookie from .exit_sudo import exit_sudo_mode + +__all__ = ["set_enable_proton_cookie", "exit_sudo_mode"] diff --git a/app/jobs/export_user_data_job.py b/app/jobs/export_user_data_job.py index 8c3701bb..48219304 100644 --- a/app/jobs/export_user_data_job.py +++ b/app/jobs/export_user_data_job.py @@ -39,7 +39,6 @@ from app.models import ( class ExportUserDataJob: - REMOVE_FIELDS = { "User": ("otp_secret", "password"), "Alias": ("ts_vector", "transfer_token", "hibp_last_check"), diff --git a/app/mail_sender.py b/app/mail_sender.py index 12b60ee9..9610b45b 100644 --- a/app/mail_sender.py +++ b/app/mail_sender.py @@ -22,7 +22,6 @@ from app.message_utils import message_to_bytes, message_format_base64_parts @dataclass class SendRequest: - SAVE_EXTENSION = "sendrequest" envelope_from: str diff --git a/app/monitor/__init__.py b/app/monitor/__init__.py index 14cd5bd9..5350f2e1 100644 --- a/app/monitor/__init__.py +++ b/app/monitor/__init__.py @@ -1 +1,3 @@ from . import views + +__all__ = ["views"] diff --git a/app/oauth/__init__.py b/app/oauth/__init__.py index 8f884340..984454bf 100644 --- a/app/oauth/__init__.py +++ b/app/oauth/__init__.py @@ -1 +1,3 @@ from .views import authorize, token, user_info + +__all__ = ["authorize", "token", "user_info"] diff --git a/app/oauth_models.py b/app/oauth_models.py index 8862adc5..7e849cc9 100644 --- a/app/oauth_models.py +++ b/app/oauth_models.py @@ -64,7 +64,7 @@ def _split_arg(arg_input: Union[str, list]) -> Set[str]: - the response_type/scope passed as a list ?scope=scope_1&scope=scope_2 """ res = set() - if type(arg_input) is str: + if isinstance(arg_input, str): if " " in arg_input: for x in arg_input.split(" "): if x: diff --git a/app/onboarding/__init__.py b/app/onboarding/__init__.py index 148c57a7..f6499e33 100644 --- a/app/onboarding/__init__.py +++ b/app/onboarding/__init__.py @@ -5,3 +5,11 @@ from .views import ( account_activated, extension_redirect, ) + +__all__ = [ + "index", + "final", + "setup_done", + "account_activated", + "extension_redirect", +] diff --git a/app/parallel_limiter.py b/app/parallel_limiter.py index e71ebef5..e245db78 100644 --- a/app/parallel_limiter.py +++ b/app/parallel_limiter.py @@ -39,7 +39,6 @@ class _InnerLock: lock_redis.storage.delete(lock_name) def __call__(self, f: Callable[..., Any]): - if self.lock_suffix is None: lock_suffix = f.__name__ else: diff --git a/app/phone/__init__.py b/app/phone/__init__.py index b959902b..b695c4ac 100644 --- a/app/phone/__init__.py +++ b/app/phone/__init__.py @@ -5,3 +5,11 @@ from .views import ( provider1_callback, provider2_callback, ) + +__all__ = [ + "index", + "phone_reservation", + "twilio_callback", + "provider1_callback", + "provider2_callback", +] diff --git a/app/redis_services.py b/app/redis_services.py index 674d24f3..9ee98a97 100644 --- a/app/redis_services.py +++ b/app/redis_services.py @@ -6,7 +6,6 @@ from app.session import RedisSessionStore def initialize_redis_services(app: flask.Flask, redis_url: str): - if redis_url.startswith("redis://") or redis_url.startswith("rediss://"): storage = limits.storage.RedisStorage(redis_url) app.session_interface = RedisSessionStore(storage.storage, storage.storage, app) diff --git a/app/session.py b/app/session.py index fb284859..481e5ff8 100644 --- a/app/session.py +++ b/app/session.py @@ -75,7 +75,7 @@ class RedisSessionStore(SessionInterface): try: data = pickle.loads(val) return ServerSession(data, session_id=session_id) - except: + except Exception: pass return ServerSession(session_id=str(uuid.uuid4())) diff --git a/cron.py b/cron.py index 2cbb8b9f..61fee4c0 100644 --- a/cron.py +++ b/cron.py @@ -105,7 +105,7 @@ def delete_logs(): rows_to_delete = EmailLog.filter(EmailLog.created_at < cutoff_time).count() expected_queries = int(rows_to_delete / batch_size) sql = text( - f"DELETE FROM email_log WHERE id IN (SELECT id FROM email_log WHERE created_at < :cutoff_time order by created_at limit :batch_size)" + "DELETE FROM email_log WHERE id IN (SELECT id FROM email_log WHERE created_at < :cutoff_time order by created_at limit :batch_size)" ) str_cutoff_time = cutoff_time.isoformat() while total_deleted < rows_to_delete: @@ -161,7 +161,7 @@ def notify_premium_end(): send_email( user.email, - f"Your subscription will end soon", + "Your subscription will end soon", render( "transactional/subscription-end.txt", user=user, @@ -218,7 +218,7 @@ def notify_manual_sub_end(): LOG.d("Remind user %s that their manual sub is ending soon", user) send_email( user.email, - f"Your subscription will end soon", + "Your subscription will end soon", render( "transactional/manual-subscription-end.txt", user=user, @@ -590,21 +590,21 @@ nb_total_bounced_last_24h: {stats_today.nb_total_bounced_last_24h} - {increase_p """ monitoring_report += "\n====================================\n" - monitoring_report += f""" + monitoring_report += """ # Account bounce report: """ for email, bounces in bounce_report(): monitoring_report += f"{email}: {bounces}\n" - monitoring_report += f"""\n + monitoring_report += """\n # Alias creation report: """ for email, nb_alias, date in alias_creation_report(): monitoring_report += f"{email}, {date}: {nb_alias}\n" - monitoring_report += f"""\n + monitoring_report += """\n # Full bounce detail report: """ monitoring_report += all_bounce_report() @@ -1099,14 +1099,14 @@ def notify_hibp(): ) LOG.d( - f"Send new breaches found email to %s for %s breaches aliases", + "Send new breaches found email to %s for %s breaches aliases", user, len(breached_aliases), ) send_email( user.email, - f"You were in a data breach", + "You were in a data breach", render( "transactional/hibp-new-breaches.txt.jinja2", user=user, diff --git a/email_handler.py b/email_handler.py index c5be1995..46e9c75d 100644 --- a/email_handler.py +++ b/email_handler.py @@ -235,7 +235,6 @@ def get_or_create_contact(from_header: str, mail_from: str, alias: Alias) -> Con contact.mail_from = mail_from Session.commit() else: - try: contact = Contact.create( user_id=alias.user_id, @@ -1197,7 +1196,7 @@ def handle_reply(envelope, msg: Message, rcpt_to: str) -> (bool, str): ) # replace reverse alias by real address for all contacts - for (reply_email, website_email) in contact_query.values( + for reply_email, website_email in contact_query.values( Contact.reply_email, Contact.website_email ): msg = replace(msg, reply_email, website_email) @@ -1952,7 +1951,7 @@ def handle_bounce(envelope, email_log: EmailLog, msg: Message) -> str: for is_delivered, smtp_status in handle_forward(envelope, msg, alias.email): res.append((is_delivered, smtp_status)) - for (is_success, smtp_status) in res: + for is_success, smtp_status in res: # Consider all deliveries successful if 1 delivery is successful if is_success: return smtp_status @@ -2272,7 +2271,7 @@ def handle(envelope: Envelope, msg: Message) -> str: if nb_success > 0 and nb_non_success > 0: LOG.e(f"some deliveries fail and some success, {mail_from}, {rcpt_tos}, {res}") - for (is_success, smtp_status) in res: + for is_success, smtp_status in res: # Consider all deliveries successful if 1 delivery is successful if is_success: return smtp_status diff --git a/poetry.lock b/poetry.lock index a1fce36d..49423284 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,4 +1,4 @@ -# This file is automatically @generated by Poetry 1.6.1 and should not be changed by hand. +# This file is automatically @generated by Poetry 1.7.0 and should not be changed by hand. [[package]] name = "aiohttp" @@ -2831,6 +2831,32 @@ files = [ docs = ["ryd"] jinja2 = ["ruamel.yaml.jinja2 (>=0.2)"] +[[package]] +name = "ruff" +version = "0.1.5" +description = "An extremely fast Python linter and code formatter, written in Rust." +optional = false +python-versions = ">=3.7" +files = [ + {file = "ruff-0.1.5-py3-none-macosx_10_7_x86_64.whl", hash = "sha256:32d47fc69261c21a4c48916f16ca272bf2f273eb635d91c65d5cd548bf1f3d96"}, + {file = "ruff-0.1.5-py3-none-macosx_10_9_x86_64.macosx_11_0_arm64.macosx_10_9_universal2.whl", hash = "sha256:171276c1df6c07fa0597fb946139ced1c2978f4f0b8254f201281729981f3c17"}, + {file = "ruff-0.1.5-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:17ef33cd0bb7316ca65649fc748acc1406dfa4da96a3d0cde6d52f2e866c7b39"}, + {file = "ruff-0.1.5-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:b2c205827b3f8c13b4a432e9585750b93fd907986fe1aec62b2a02cf4401eee6"}, + {file = "ruff-0.1.5-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bb408e3a2ad8f6881d0f2e7ad70cddb3ed9f200eb3517a91a245bbe27101d379"}, + {file = "ruff-0.1.5-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:f20dc5e5905ddb407060ca27267c7174f532375c08076d1a953cf7bb016f5a24"}, + {file = "ruff-0.1.5-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:aafb9d2b671ed934998e881e2c0f5845a4295e84e719359c71c39a5363cccc91"}, + {file = "ruff-0.1.5-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a4894dddb476597a0ba4473d72a23151b8b3b0b5f958f2cf4d3f1c572cdb7af7"}, + {file = "ruff-0.1.5-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a00a7ec893f665ed60008c70fe9eeb58d210e6b4d83ec6654a9904871f982a2a"}, + {file = "ruff-0.1.5-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:a8c11206b47f283cbda399a654fd0178d7a389e631f19f51da15cbe631480c5b"}, + {file = "ruff-0.1.5-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:fa29e67b3284b9a79b1a85ee66e293a94ac6b7bb068b307a8a373c3d343aa8ec"}, + {file = "ruff-0.1.5-py3-none-musllinux_1_2_i686.whl", hash = "sha256:9b97fd6da44d6cceb188147b68db69a5741fbc736465b5cea3928fdac0bc1aeb"}, + {file = "ruff-0.1.5-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:721f4b9d3b4161df8dc9f09aa8562e39d14e55a4dbaa451a8e55bdc9590e20f4"}, + {file = "ruff-0.1.5-py3-none-win32.whl", hash = "sha256:f80c73bba6bc69e4fdc73b3991db0b546ce641bdcd5b07210b8ad6f64c79f1ab"}, + {file = "ruff-0.1.5-py3-none-win_amd64.whl", hash = "sha256:c21fe20ee7d76206d290a76271c1af7a5096bc4c73ab9383ed2ad35f852a0087"}, + {file = "ruff-0.1.5-py3-none-win_arm64.whl", hash = "sha256:82bfcb9927e88c1ed50f49ac6c9728dab3ea451212693fe40d08d314663e412f"}, + {file = "ruff-0.1.5.tar.gz", hash = "sha256:5cbec0ef2ae1748fb194f420fb03fb2c25c3258c86129af7172ff8f198f125ab"}, +] + [[package]] name = "s3transfer" version = "0.3.3" @@ -3674,4 +3700,4 @@ testing = ["coverage (>=5.0.3)", "zope.event", "zope.testing"] [metadata] lock-version = "2.0" python-versions = "^3.10" -content-hash = "8bf71c74c8f4d1afe6b1ab0912702cdb47086474168bed8a9230c398abf349dd" +content-hash = "01afc410d21eeac0a0ac7e8ef6eeb0a991cf4bc091c3351049263462e205ff63" diff --git a/pyproject.toml b/pyproject.toml index 0ef202b8..f5bccebe 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -18,6 +18,9 @@ exclude = ''' ) ''' +[tool.ruff] +ignore-init-module-imports = true + [tool.djlint] indent = 2 profile = "jinja" @@ -121,6 +124,9 @@ black = "^22.1.0" djlint = "^1.3.0" pylint = "^2.14.4" +[tool.poetry.group.dev.dependencies] +ruff = "^0.1.5" + [build-system] requires = ["poetry>=0.12"] build-backend = "poetry.masonry.api" diff --git a/server.py b/server.py index d6bb00ec..9f62e5a4 100644 --- a/server.py +++ b/server.py @@ -641,7 +641,7 @@ def setup_paddle_callback(app: Flask): @app.route("/paddle_coupon", methods=["GET", "POST"]) def paddle_coupon(): - LOG.d(f"paddle coupon callback %s", request.form) + LOG.d("paddle coupon callback %s", request.form) if not paddle_utils.verify_incoming_request(dict(request.form)): LOG.e("request not coming from paddle. Request data:%s", dict(request.form)) diff --git a/shell.py b/shell.py index beff583a..54336411 100644 --- a/shell.py +++ b/shell.py @@ -1,13 +1,12 @@ -from time import sleep - import flask_migrate from IPython import embed from sqlalchemy_utils import create_database, database_exists, drop_database from app import models from app.config import DB_URI -from app.models import * - +from app.db import Session +from app.log import LOG +from app.models import User, RecoveryCode if False: # noinspection PyUnreachableCode diff --git a/tests/api/test_alias_options.py b/tests/api/test_alias_options.py index de253cb7..42cffd23 100644 --- a/tests/api/test_alias_options.py +++ b/tests/api/test_alias_options.py @@ -58,7 +58,7 @@ def test_different_scenarios_v4_2(flask_client): assert r.json["suffixes"] assert r.json["prefix_suggestion"] == "" # no hostname => no suggestion - for (suffix, signed_suffix) in r.json["suffixes"]: + for suffix, signed_suffix in r.json["suffixes"]: assert signed_suffix.startswith(suffix) # <<< with hostname >>> diff --git a/tests/jobs/test_job_runner.py b/tests/jobs/test_job_runner.py index fc5aa6ba..65422e64 100644 --- a/tests/jobs/test_job_runner.py +++ b/tests/jobs/test_job_runner.py @@ -56,13 +56,15 @@ def test_get_jobs_to_run(flask_client): run_at=now.shift(hours=3), ) # Job out of attempts - Job.create( - name="", - payload="", - state=JobState.taken.value, - taken_at=now.shift(minutes=-(config.JOB_TAKEN_RETRY_WAIT_MINS + 10)), - attempts=config.JOB_MAX_ATTEMPTS + 1, - ), + ( + Job.create( + name="", + payload="", + state=JobState.taken.value, + taken_at=now.shift(minutes=-(config.JOB_TAKEN_RETRY_WAIT_MINS + 10)), + attempts=config.JOB_MAX_ATTEMPTS + 1, + ), + ) Session.commit() jobs = get_jobs_to_run() assert len(jobs) == len(expected_jobs_to_run) diff --git a/tests/test_email_utils.py b/tests/test_email_utils.py index e33b3fb1..9adefc55 100644 --- a/tests/test_email_utils.py +++ b/tests/test_email_utils.py @@ -7,7 +7,7 @@ import arrow import pytest from app import config -from app.config import MAX_ALERT_24H, EMAIL_DOMAIN, ROOT_DIR +from app.config import MAX_ALERT_24H, ROOT_DIR from app.db import Session from app.email_utils import ( get_email_domain_part, @@ -16,7 +16,6 @@ from app.email_utils import ( delete_header, add_or_replace_header, send_email_with_rate_control, - copy, get_spam_from_header, get_header_from_bounce, add_header, diff --git a/tests/test_jose_utils.py b/tests/test_jose_utils.py index d8a20c73..ad78db17 100644 --- a/tests/test_jose_utils.py +++ b/tests/test_jose_utils.py @@ -17,7 +17,7 @@ def test_encode_decode(flask_client): jwt_token = make_id_token(client_user) - assert type(jwt_token) is str + assert isinstance(jwt_token, str) assert verify_id_token(jwt_token) diff --git a/tests/test_pgp_utils.py b/tests/test_pgp_utils.py index df4be522..42bcfacd 100644 --- a/tests/test_pgp_utils.py +++ b/tests/test_pgp_utils.py @@ -49,9 +49,9 @@ def encrypt_decrypt_text(text: str): priv = pgpy.PGPKey() priv.parse(private_key) decrypted = priv.decrypt(encrypted).message - if type(decrypted) == str: + if isinstance(decrypted, str): assert decrypted == text - elif type(decrypted) == bytearray: + elif isinstance(decrypted, bytearray): assert decrypted.decode() == text