diff --git a/app/app.py b/app/app.py index 3c66a05..709e48f 100644 --- a/app/app.py +++ b/app/app.py @@ -7,14 +7,15 @@ the Flask application instance with comprehensive security hardening. import os import secrets -from flask import Flask, g, request, redirect, jsonify, render_template, url_for -from flask_cors import CORS -from app.extensions import db, login_manager, csrf, limiter, babel -from sqlalchemy import text -from werkzeug.exceptions import HTTPException import markupsafe from dotenv import load_dotenv +from flask import Flask, g, jsonify, redirect, render_template, request, url_for +from flask_cors import CORS +from sqlalchemy import text +from werkzeug.exceptions import HTTPException + from app import i18n +from app.extensions import babel, csrf, db, limiter, login_manager load_dotenv() @@ -265,13 +266,13 @@ def create_app(config=None): configure_logging(app) from app.routes.auth import auth_bp - from app.routes.tryouts import tryouts_bp from app.routes.evaluations import evaluations_bp - from app.routes.users import users_bp from app.routes.main import main_bp - from app.routes.teams import teams_bp from app.routes.matches import matches_bp from app.routes.team_matches import team_matches_bp + from app.routes.teams import teams_bp + from app.routes.tryouts import tryouts_bp + from app.routes.users import users_bp app.register_blueprint(auth_bp) app.register_blueprint(tryouts_bp) diff --git a/app/discord_bot.py b/app/discord_bot.py index 3e10e6a..12604a0 100644 --- a/app/discord_bot.py +++ b/app/discord_bot.py @@ -6,21 +6,22 @@ This module provides a persistent bot that handles: - Daily reminders at 18:00 EDT for upcoming events """ -import os +import asyncio import json import logging +import os import tempfile -import time -import asyncio import threading +import time import traceback from datetime import datetime, timedelta +from queue import Empty, Queue from zoneinfo import ZoneInfo -from queue import Queue, Empty -from discord import Intents -from discord.ext import commands + from apscheduler.schedulers.asyncio import AsyncIOScheduler from apscheduler.triggers.cron import CronTrigger +from discord import Intents +from discord.ext import commands from dotenv import load_dotenv load_dotenv() @@ -453,8 +454,8 @@ class TeamTryoutsBot(commands.Bot): async def handle_one_on_one_approve(self, coach, message_id, request_id, channel): """Handle coach approving a One on One request.""" try: - from app.models import OneOnOneRequest from app.extensions import db + from app.models import OneOnOneRequest request = OneOnOneRequest.query.get(request_id) if not request: @@ -498,8 +499,8 @@ class TeamTryoutsBot(commands.Bot): async def handle_one_on_one_reject(self, coach, message_id, request_id, channel): """Handle coach rejecting a One on One request.""" try: - from app.models import OneOnOneRequest from app.extensions import db + from app.models import OneOnOneRequest request = OneOnOneRequest.query.get(request_id) if not request: @@ -743,14 +744,15 @@ class TeamTryoutsBot(commands.Bot): async def _send_daily_reminders_impl(self): """Internal implementation of daily reminders with proper app context.""" try: + from sqlalchemy.orm import joinedload + from app.models import ( Match, - Tryout, MatchParticipant, - TryoutRegistration, OneOnOneRequest, + Tryout, + TryoutRegistration, ) - from sqlalchemy.orm import joinedload now = datetime.now(self.timezone) tomorrow = now.date() + timedelta(days=1) diff --git a/app/extensions.py b/app/extensions.py index d262e4b..c6ad0e1 100644 --- a/app/extensions.py +++ b/app/extensions.py @@ -1,10 +1,10 @@ -from flask_sqlalchemy import SQLAlchemy -from flask_login import LoginManager -from flask_wtf.csrf import CSRFProtect -from werkzeug.security import generate_password_hash, check_password_hash +from flask_babel import Babel from flask_limiter import Limiter from flask_limiter.util import get_remote_address -from flask_babel import Babel +from flask_login import LoginManager +from flask_sqlalchemy import SQLAlchemy +from flask_wtf.csrf import CSRFProtect +from werkzeug.security import check_password_hash, generate_password_hash # Database and extension initialization db = SQLAlchemy() diff --git a/app/logging_config.py b/app/logging_config.py index 003365c..2dd0616 100644 --- a/app/logging_config.py +++ b/app/logging_config.py @@ -11,8 +11,8 @@ Usage: import logging import os -from logging.handlers import RotatingFileHandler import re +from logging.handlers import RotatingFileHandler class SensitiveDataFilter(logging.Filter): diff --git a/app/models/_associations.py b/app/models/_associations.py index 207fa54..d41cc8b 100644 --- a/app/models/_associations.py +++ b/app/models/_associations.py @@ -2,7 +2,6 @@ from app.extensions import db - org_team_coaches = db.Table( 'org_team_coaches', db.Column( diff --git a/app/models/availability/__init__.py b/app/models/availability/__init__.py index b60623d..ae053f8 100644 --- a/app/models/availability/__init__.py +++ b/app/models/availability/__init__.py @@ -1,7 +1,7 @@ """Availability models — BaseAvailability and its concrete subclasses.""" from app.models.availability.base import BaseAvailability -from app.models.availability.player_disponibility import PlayerDisponibility from app.models.availability.coach_availability import CoachAvailability +from app.models.availability.player_disponibility import PlayerDisponibility __all__ = ['BaseAvailability', 'PlayerDisponibility', 'CoachAvailability'] diff --git a/app/models/availability/base.py b/app/models/availability/base.py index 6fc6c4e..4aee7ce 100644 --- a/app/models/availability/base.py +++ b/app/models/availability/base.py @@ -1,8 +1,9 @@ """Abstract base class for availability models (PlayerDisponibility + CoachAvailability).""" -from app.extensions import db from datetime import datetime +from app.extensions import db + class BaseAvailability(db.Model): """Shared schema for player disponibilities and coach availabilities.""" diff --git a/app/models/contract.py b/app/models/contract.py index e1a97ca..889a9ac 100644 --- a/app/models/contract.py +++ b/app/models/contract.py @@ -1,8 +1,9 @@ """Contract documents for players to sign.""" -from app.extensions import db from datetime import datetime +from app.extensions import db + class Contract(db.Model): """Contract documents for players to sign.""" @@ -48,8 +49,8 @@ class Contract(db.Model): return True from app.models.user_model.admin import Admin - from app.models.user_model.manager import Manager from app.models.user_model.coach import Coach + from app.models.user_model.manager import Manager from app.models.user_model.user import User from app.permissions import coach_can_access_player diff --git a/app/models/evaluation.py b/app/models/evaluation.py index 8127d6e..0492eb9 100644 --- a/app/models/evaluation.py +++ b/app/models/evaluation.py @@ -1,8 +1,9 @@ """Player evaluation record.""" -from app.extensions import db from datetime import datetime +from app.extensions import db + class Evaluation(db.Model): """Player evaluation record.""" diff --git a/app/models/match_model/base.py b/app/models/match_model/base.py index ca63991..f95232c 100644 --- a/app/models/match_model/base.py +++ b/app/models/match_model/base.py @@ -1,8 +1,9 @@ """Abstract base class for match models (Match + TeamMatch).""" -from app.extensions import db from datetime import datetime +from app.extensions import db + class BaseMatch(db.Model): """Shared schema for tryout-scoped matches and regular-season team matches.""" diff --git a/app/models/one_on_one_request.py b/app/models/one_on_one_request.py index 2a726ca..1b1e94f 100644 --- a/app/models/one_on_one_request.py +++ b/app/models/one_on_one_request.py @@ -1,8 +1,9 @@ """Request from player to coach for a One on One session.""" -from app.extensions import db from datetime import datetime +from app.extensions import db + class OneOnOneRequest(db.Model): """Request from player to coach for a One on One session.""" diff --git a/app/models/org_team/org_team.py b/app/models/org_team/org_team.py index 86e9007..ae0a0b1 100644 --- a/app/models/org_team/org_team.py +++ b/app/models/org_team/org_team.py @@ -1,8 +1,9 @@ """Persistent organisation team (e.g. Varsity, JV).""" +from datetime import datetime + from app.extensions import db from app.models._associations import org_team_coaches, org_team_managers -from datetime import datetime class OrgTeam(db.Model): diff --git a/app/models/org_team/team_player.py b/app/models/org_team/team_player.py index c36fe7b..d07865c 100644 --- a/app/models/org_team/team_player.py +++ b/app/models/org_team/team_player.py @@ -1,8 +1,9 @@ """Many-to-many junction: player to org-team.""" -from app.extensions import db from datetime import datetime +from app.extensions import db + class TeamPlayer(db.Model): """Many-to-many: player to org-team.""" diff --git a/app/models/participant/base.py b/app/models/participant/base.py index 6d5d99c..7293699 100644 --- a/app/models/participant/base.py +++ b/app/models/participant/base.py @@ -1,8 +1,9 @@ """Abstract base class for match participant models.""" -from app.extensions import db from datetime import datetime +from app.extensions import db + class BaseParticipant(db.Model): """Shared schema for match participants.""" diff --git a/app/models/personal_note.py b/app/models/personal_note.py index b8c3bc8..4745202 100644 --- a/app/models/personal_note.py +++ b/app/models/personal_note.py @@ -1,8 +1,9 @@ """Personal notes from coach to individual player.""" -from app.extensions import db from datetime import datetime +from app.extensions import db + class PersonalNote(db.Model): """Personal notes from coach to individual player.""" diff --git a/app/models/team/team.py b/app/models/team/team.py index d243fdc..4611fe1 100644 --- a/app/models/team/team.py +++ b/app/models/team/team.py @@ -1,8 +1,9 @@ """Tryout-specific team (e.g. Alpha, Bravo within a single tryout).""" -from app.extensions import db from datetime import datetime +from app.extensions import db + class Team(db.Model): """Tryout-specific team (e.g. Alpha, Bravo within a single tryout).""" diff --git a/app/models/team/team_member.py b/app/models/team/team_member.py index 8861298..391048f 100644 --- a/app/models/team/team_member.py +++ b/app/models/team/team_member.py @@ -1,8 +1,9 @@ """Link between a player and a tryout-specific team.""" -from app.extensions import db from datetime import datetime +from app.extensions import db + class TeamMember(db.Model): """Link between a player and a tryout-specific team.""" diff --git a/app/models/team_note.py b/app/models/team_note.py index 46ebbfb..76c8c21 100644 --- a/app/models/team_note.py +++ b/app/models/team_note.py @@ -1,8 +1,9 @@ """Team improvement notes from coach.""" -from app.extensions import db from datetime import datetime +from app.extensions import db + class TeamNote(db.Model): """Team improvement notes from coach.""" diff --git a/app/models/tryout/tryout.py b/app/models/tryout/tryout.py index 3d34a49..e9f4a74 100644 --- a/app/models/tryout/tryout.py +++ b/app/models/tryout/tryout.py @@ -1,8 +1,9 @@ """Tryout event for player evaluations and team formation.""" +from datetime import datetime + from app.extensions import db from app.models._associations import tryout_coaches -from datetime import datetime class Tryout(db.Model): diff --git a/app/models/tryout/tryout_registration.py b/app/models/tryout/tryout_registration.py index 162f7d8..007e77b 100644 --- a/app/models/tryout/tryout_registration.py +++ b/app/models/tryout/tryout_registration.py @@ -1,8 +1,9 @@ """Registration linking a player to a tryout.""" -from app.extensions import db from datetime import datetime +from app.extensions import db + class TryoutRegistration(db.Model): """Registration linking a player to a tryout.""" diff --git a/app/models/user_gamertag.py b/app/models/user_gamertag.py index 8c2c591..589d32d 100644 --- a/app/models/user_gamertag.py +++ b/app/models/user_gamertag.py @@ -1,9 +1,10 @@ """Store gamertag per game for each user.""" -from app.extensions import db -from app.models._constants import TRN_URLS, PLATFORM_CODES, PLATFORM_DEFAULTS from urllib.parse import quote +from app.extensions import db +from app.models._constants import PLATFORM_CODES, PLATFORM_DEFAULTS, TRN_URLS + class UserGamertag(db.Model): """Store gamertag per game for each user.""" diff --git a/app/models/user_model/__init__.py b/app/models/user_model/__init__.py index 2a81dbb..a1e2670 100644 --- a/app/models/user_model/__init__.py +++ b/app/models/user_model/__init__.py @@ -1,10 +1,10 @@ """User hierarchy — single-table polymorphic inheritance (User → Admin, Manager, Coach, Player, Scout).""" -from app.models.user_model.user import User from app.models.user_model.admin import Admin -from app.models.user_model.manager import Manager from app.models.user_model.coach import Coach +from app.models.user_model.manager import Manager from app.models.user_model.player import Player from app.models.user_model.scout import Scout +from app.models.user_model.user import User __all__ = ['User', 'Admin', 'Manager', 'Coach', 'Player', 'Scout'] diff --git a/app/models/user_model/manager.py b/app/models/user_model/manager.py index 10a7b94..aa95fb3 100644 --- a/app/models/user_model/manager.py +++ b/app/models/user_model/manager.py @@ -27,9 +27,10 @@ class Manager(User): return True def get_visible_tryouts(self): - from app.models.tryout.tryout import Tryout from sqlalchemy import or_ + from app.models.tryout.tryout import Tryout + return ( Tryout.query.filter(or_(Tryout.created_by == self.id, Tryout.manager_id == self.id)) .order_by(Tryout.date) diff --git a/app/models/user_model/player.py b/app/models/user_model/player.py index 30229c0..a3ceeea 100644 --- a/app/models/user_model/player.py +++ b/app/models/user_model/player.py @@ -9,9 +9,9 @@ class Player(User): __mapper_args__ = {'polymorphic_identity': 'player'} def get_visible_tryouts(self): - from app.models.tryout.tryout import Tryout from app.models.match_model.match import Match from app.models.participant.match_participant import MatchParticipant + from app.models.tryout.tryout import Tryout # tryouts they registered for player_tryout_ids = [r.tryout_id for r in self.tryout_registrations.all()] diff --git a/app/models/user_model/user.py b/app/models/user_model/user.py index 3a27544..231a726 100644 --- a/app/models/user_model/user.py +++ b/app/models/user_model/user.py @@ -1,9 +1,11 @@ """Base User model — shared fields and polymorphic configuration.""" -from app.extensions import db -from flask_login import UserMixin from datetime import datetime +from flask_login import UserMixin + +from app.extensions import db + class User(UserMixin, db.Model): """Base user model — shared fields for every role. diff --git a/app/permissions.py b/app/permissions.py index 00bfe7f..b0d39ae 100644 --- a/app/permissions.py +++ b/app/permissions.py @@ -28,7 +28,6 @@ and from tests without a request context. from app.extensions import db - # --------------------------------------------------------------------------- # Team attachment # --------------------------------------------------------------------------- diff --git a/app/routes/auth.py b/app/routes/auth.py index 0ad9015..ff0d383 100644 --- a/app/routes/auth.py +++ b/app/routes/auth.py @@ -5,21 +5,23 @@ protection, logout with session clearing, and new user registration with password policy enforcement and CAPTCHA verification. """ -import uuid import os import secrets +import uuid from datetime import datetime, timedelta -from flask import Blueprint, render_template, redirect, url_for, flash, request, session -from flask_login import login_user, logout_user, login_required, current_user -from app.extensions import db, hash_password, check_password, limiter -from app.models import User, Player, ESPORT_GAMES -from app.validators import RegisterSchema, LoginSchema +from urllib.parse import urlencode, urlparse + +import requests +from flask import Blueprint, flash, redirect, render_template, request, session, url_for +from flask_babel import gettext as _ +from flask_login import current_user, login_required, login_user, logout_user +from marshmallow import ValidationError + +from app.extensions import check_password, db, hash_password, limiter from app.i18n import LOCALE_SESSION_KEY from app.logging_config import log_auth_event -from flask_babel import gettext as _ -from marshmallow import ValidationError -from urllib.parse import urlparse, urlencode -import requests +from app.models import ESPORT_GAMES, Player, User +from app.validators import LoginSchema, RegisterSchema #: Session key holding the pending OAuth2 anti-forgery token. DISCORD_STATE_KEY = 'discord_oauth_state' diff --git a/app/routes/evaluations.py b/app/routes/evaluations.py index ba696c8..2f57a4b 100644 --- a/app/routes/evaluations.py +++ b/app/routes/evaluations.py @@ -3,22 +3,23 @@ Uses polymorphic isinstance checks instead of role-string comparisons. """ -from flask import Blueprint, render_template, redirect, url_for, flash, request -from flask_login import login_required, current_user +from flask import Blueprint, flash, redirect, render_template, request, url_for from flask_babel import gettext as _ -from app.extensions import db -from app.models import ( - Admin, - Player, - User, - Tryout, - Evaluation, - TryoutRegistration, - GAME_POSITIONS, -) +from flask_login import current_user, login_required from sqlalchemy import func from sqlalchemy.orm import aliased +from app.extensions import db +from app.models import ( + GAME_POSITIONS, + Admin, + Evaluation, + Player, + Tryout, + TryoutRegistration, + User, +) + evaluations_bp = Blueprint('evaluations', __name__, url_prefix='/evaluations') diff --git a/app/routes/main.py b/app/routes/main.py index 8b85347..2691b3d 100644 --- a/app/routes/main.py +++ b/app/routes/main.py @@ -3,27 +3,29 @@ Uses polymorphic isinstance checks instead of role-string comparisons. """ -from flask import Blueprint, render_template, redirect, url_for, flash, request -from flask_login import login_required, current_user +from datetime import date + +from flask import Blueprint, flash, redirect, render_template, request, url_for from flask_babel import gettext as _ +from flask_login import current_user, login_required +from sqlalchemy import func + from app.extensions import db from app.models import ( Admin, - Manager, Coach, - Player, - Scout, - User, - Tryout, Evaluation, - TryoutRegistration, - TeamMember, + Manager, Match, MatchParticipant, + Player, + Scout, + TeamMember, + Tryout, + TryoutRegistration, + User, ) from app.permissions import coach_tryout_ids -from sqlalchemy import func -from datetime import date main_bp = Blueprint('main', __name__) diff --git a/app/routes/matches.py b/app/routes/matches.py index 1488f5a..8101bc0 100644 --- a/app/routes/matches.py +++ b/app/routes/matches.py @@ -3,30 +3,31 @@ Uses polymorphic isinstance checks instead of role-string comparisons. """ -from flask import Blueprint, render_template, redirect, url_for, flash, request, jsonify -from flask_login import login_required, current_user +from datetime import datetime, timedelta + +from flask import Blueprint, flash, jsonify, redirect, render_template, request, url_for from flask_babel import gettext as _ +from flask_login import current_user, login_required +from sqlalchemy.orm import joinedload + from app.extensions import db from app.models import ( Admin, - Manager, Coach, - Player, - Scout, - User, - Tryout, + Manager, Match, MatchParticipant, - Team, - TeamMember, - TryoutRegistration, - PlayerDisponibility, OneOnOneRequest, PersonalNote, + Player, + PlayerDisponibility, + Scout, + Team, + TeamMember, + Tryout, + TryoutRegistration, + User, ) -from datetime import datetime, timedelta - -from sqlalchemy.orm import joinedload from app.services.scheduling import notify_participants, zip_participants matches_bp = Blueprint('matches', __name__, url_prefix='/matches') diff --git a/app/routes/team_matches.py b/app/routes/team_matches.py index 584a473..1d28c0f 100644 --- a/app/routes/team_matches.py +++ b/app/routes/team_matches.py @@ -3,22 +3,24 @@ Uses polymorphic isinstance checks instead of role-string comparisons. """ -from flask import Blueprint, render_template, redirect, url_for, flash, request, jsonify -from flask_login import login_required, current_user +from datetime import datetime, timedelta + +from flask import Blueprint, flash, jsonify, redirect, render_template, request, url_for from flask_babel import gettext as _ +from flask_login import current_user, login_required + from app.extensions import db from app.models import ( Admin, - Manager, Coach, - Player, + Manager, OrgTeam, + Player, TeamMatch, TeamMatchParticipant, TeamPlayer, ) from app.permissions import can_manage_org_team, coach_org_teams, visible_org_teams -from datetime import datetime, timedelta from app.services.scheduling import notify_participants, zip_participants team_matches_bp = Blueprint('team_matches', __name__, url_prefix='/team-matches') diff --git a/app/routes/teams.py b/app/routes/teams.py index d038437..62011f5 100644 --- a/app/routes/teams.py +++ b/app/routes/teams.py @@ -3,27 +3,29 @@ Uses polymorphic isinstance checks instead of role-string comparisons. """ -from flask import Blueprint, render_template, redirect, url_for, flash, request, jsonify -from flask_login import login_required, current_user +from datetime import datetime + +from flask import Blueprint, flash, jsonify, redirect, render_template, request, url_for from flask_babel import gettext as _ +from flask_login import current_user, login_required + from app.extensions import db from app.models import ( Admin, - Manager, Coach, - Player, - OrgTeam, - User, - PersonalNote, - TeamNote, - Tryout, - TeamPlayer, - TeamMatch, Contract, + Manager, OneOnOneRequest, + OrgTeam, + PersonalNote, + Player, + TeamMatch, + TeamNote, + TeamPlayer, + Tryout, + User, ) from app.permissions import visible_org_teams -from datetime import datetime teams_bp = Blueprint('teams', __name__, url_prefix='/teams') diff --git a/app/routes/tryouts.py b/app/routes/tryouts.py index 84b6e7b..d304bb2 100644 --- a/app/routes/tryouts.py +++ b/app/routes/tryouts.py @@ -4,30 +4,32 @@ This module handles CRUD operations for tryouts and player registrations. Uses polymorphic isinstance checks instead of role-string comparisons. """ -from flask import Blueprint, render_template, redirect, url_for, flash, request, abort -from flask_login import login_required, current_user +from datetime import datetime + +from flask import Blueprint, abort, flash, redirect, render_template, request, url_for from flask_babel import gettext as _ +from flask_login import current_user, login_required + from app.extensions import db from app.models import ( - Admin, - Manager, - Coach, - Player, - Scout, - User, - Tryout, - TryoutRegistration, - Evaluation, - Team, - TeamMember, - OrgTeam, - Match, - MatchParticipant, - PersonalNote, ESPORT_GAMES, GAME_POSITIONS, + Admin, + Coach, + Evaluation, + Manager, + Match, + MatchParticipant, + OrgTeam, + PersonalNote, + Player, + Scout, + Team, + TeamMember, + Tryout, + TryoutRegistration, + User, ) -from datetime import datetime tryouts_bp = Blueprint('tryouts', __name__, url_prefix='/tryouts') diff --git a/app/routes/users/__init__.py b/app/routes/users/__init__.py index 9af3bf6..7fb7bbd 100644 --- a/app/routes/users/__init__.py +++ b/app/routes/users/__init__.py @@ -9,16 +9,16 @@ keeps its single `from app.routes.users import users_bp`. The blueprint itself lives in blueprint.py to keep that import one-directional. """ -from app.routes.users.blueprint import users_bp - # Imported for their side effect: each module attaches its routes to # users_bp. Order does not matter; none of them import each other. -from app.routes.users import accounts # noqa: F401,E402 -from app.routes.users import availability # noqa: F401,E402 -from app.routes.users import contracts # noqa: F401,E402 -from app.routes.users import notes # noqa: F401,E402 -from app.routes.users import one_on_one # noqa: F401,E402 -from app.routes.users import profile # noqa: F401,E402 +from app.routes.users import ( + accounts, # noqa: F401,E402 + availability, # noqa: F401,E402 + contracts, # noqa: F401,E402 + notes, # noqa: F401,E402 + one_on_one, # noqa: F401,E402 + profile, # noqa: F401,E402 +) # Re-exported because tests and other modules reach for them by name. from app.routes.users._shared import ( # noqa: F401,E402 @@ -26,6 +26,7 @@ from app.routes.users._shared import ( # noqa: F401,E402 ALLOWED_SIGNED_EXTENSIONS, pdf_upload_error, ) +from app.routes.users.blueprint import users_bp __all__ = [ 'ALLOWED_CONTRACT_EXTENSIONS', diff --git a/app/routes/users/_shared.py b/app/routes/users/_shared.py index 7951ea1..72a851e 100644 --- a/app/routes/users/_shared.py +++ b/app/routes/users/_shared.py @@ -8,7 +8,7 @@ from flask import flash, request from flask_babel import gettext as _ from app.extensions import db -from app.models import Admin, Coach, Manager, Player, Scout, GAME_PLATFORMS, UserGamertag +from app.models import GAME_PLATFORMS, Admin, Coach, Manager, Player, Scout, UserGamertag ALLOWED_CONTRACT_EXTENSIONS = {'pdf'} ALLOWED_SIGNED_EXTENSIONS = {'pdf'} diff --git a/app/routes/users/accounts.py b/app/routes/users/accounts.py index 5bb4caa..1d431f8 100644 --- a/app/routes/users/accounts.py +++ b/app/routes/users/accounts.py @@ -12,12 +12,13 @@ from marshmallow import ValidationError from app.extensions import db, hash_password from app.logging_config import log_auth_event from app.models import ( + ESPORT_GAMES, + GAME_PLATFORMS, + USER_TYPES, Admin, CoachAvailability, Contract, - ESPORT_GAMES, Evaluation, - GAME_PLATFORMS, Match, MatchParticipant, OneOnOneRequest, @@ -31,7 +32,6 @@ from app.models import ( TeamPlayer, Tryout, TryoutRegistration, - USER_TYPES, User, UserGamertag, ) diff --git a/app/routes/users/availability.py b/app/routes/users/availability.py index 9ec1aa4..de594a2 100644 --- a/app/routes/users/availability.py +++ b/app/routes/users/availability.py @@ -15,7 +15,6 @@ from app.extensions import db from app.models import Coach, CoachAvailability, PlayerDisponibility, User from app.routes.users.blueprint import users_bp - DAY_NAMES = ['Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday', 'Sunday'] diff --git a/app/routes/users/one_on_one.py b/app/routes/users/one_on_one.py index 1f76024..6ff2829 100644 --- a/app/routes/users/one_on_one.py +++ b/app/routes/users/one_on_one.py @@ -119,7 +119,8 @@ def one_on_one(): return redirect(url_for('users.one_on_one')) # Build list of upcoming dates that have coach availability - from datetime import date as date_cls, timedelta as td + from datetime import date as date_cls + from datetime import timedelta as td today = date_cls.today() available_days = {av['day_of_week'] for av in coach_availability} diff --git a/app/routes/users/profile.py b/app/routes/users/profile.py index 69c4f33..2898431 100644 --- a/app/routes/users/profile.py +++ b/app/routes/users/profile.py @@ -8,11 +8,11 @@ from marshmallow import ValidationError from app.extensions import db, hash_password from app.logging_config import log_auth_event from app.models import ( - CoachAvailability, - Coach, - Contract, ESPORT_GAMES, GAME_PLATFORMS, + Coach, + CoachAvailability, + Contract, Player, User, ) diff --git a/app/supporting_scripts/run_https.py b/app/supporting_scripts/run_https.py index 788ca91..4ea3e8e 100644 --- a/app/supporting_scripts/run_https.py +++ b/app/supporting_scripts/run_https.py @@ -13,10 +13,12 @@ Accept the self-signed certificate warning in your browser to proceed. import os import socket -import subprocess import ssl +import subprocess import sys + from waitress.server import create_server + from app.app import create_app CERT_FILE = 'certs/localhost.pem' diff --git a/app/supporting_scripts/security_scan.py b/app/supporting_scripts/security_scan.py index e783462..dc8afa9 100644 --- a/app/supporting_scripts/security_scan.py +++ b/app/supporting_scripts/security_scan.py @@ -12,12 +12,12 @@ Usage: python security_scan.py [--url http://localhost:5000] """ -import os -import sys import json -import subprocess -import urllib.request +import os import ssl +import subprocess +import sys +import urllib.request from datetime import datetime diff --git a/app/validators.py b/app/validators.py index a22c886..1c6e981 100644 --- a/app/validators.py +++ b/app/validators.py @@ -11,18 +11,19 @@ Usage: """ import re + from flask_babel import lazy_gettext as _l from marshmallow import ( - Schema, - fields, - validate, - ValidationError, - pre_load, - validates_schema, EXCLUDE, + Schema, + ValidationError, + fields, + pre_load, + validate, + validates_schema, ) -from app.models import USER_TYPES +from app.models import USER_TYPES # ============================================================================= # Custom Validators diff --git a/pyproject.toml b/pyproject.toml index a29f5f6..a954859 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -42,11 +42,10 @@ exclude = [".venv", "venv", "migrations", "docs"] # RET return-flow tidiness # SIM obvious simplifications # UP syntax available on the 3.12 this targets -# -# isort (I) is not enabled yet: it would reorder imports in 48 files, i.e. a -# second sweep of pure churn right after the formatting commit. Worth doing, -# worth doing on its own. -select = ["E4", "E7", "E9", "F", "B", "C4", "RET", "SIM", "UP"] +# I isort; enabled in its own commit, for the same reason the +# formatting got one — the diff is churn and must not hide +# behind a behavioural change +select = ["E4", "E7", "E9", "F", "B", "C4", "RET", "SIM", "UP", "I"] # Forcing a ternary reads worse than the if/else it replaces in the one place # it fires (evaluations.py, choosing a sort direction). @@ -56,7 +55,14 @@ ignore = ["SIM108"] # Intentional re-export facade: `from app.models import User, Tryout, ...` # is the documented entry point, and importing the modules is what # registers every model with SQLAlchemy. -"app/models/__init__.py" = ["F401"] +# +# I001 is off here too. The imports are grouped into eleven commented +# layers that spell out the dependency graph (constants → users → teams → +# tryouts → matches → participants). Alphabetising them leaves every +# heading above an import it does not describe, and the file's one job is +# to be read. The order is documentation, not a requirement: the suite +# passes either way. +"app/models/__init__.py" = ["F401", "I001"] "app/models/user_model/__init__.py" = ["F401"] "tests/conftest.py" = ["E402"] diff --git a/tests/test_authorization.py b/tests/test_authorization.py index 1e16df4..a865114 100644 --- a/tests/test_authorization.py +++ b/tests/test_authorization.py @@ -98,9 +98,10 @@ class TestHorizontalAccess: def test_player_cannot_delete_another_players_availability( self, app, client, as_role, make_user ): - from app.models import PlayerDisponibility from datetime import time + from app.models import PlayerDisponibility + owner_id = make_user('player') with app.app_context(): slot = PlayerDisponibility( diff --git a/wsgi.py b/wsgi.py index d9d6dfe..791a5dd 100644 --- a/wsgi.py +++ b/wsgi.py @@ -12,8 +12,9 @@ Configuration via environment variables: WAITRESS_THREADS: Number of worker threads (default: CPU*2+1) """ -import os import multiprocessing +import os + from app.app import create_app app = create_app()