supercontest.dbsession.commits
The writes: each function stages one change in the caller’s session.
Every function takes the caller’s AsyncSession first, and flushes, never
commits. The route, job or script that owns the unit of work commits it once,
when every write in it has succeeded – so a failure part-way leaves the
database as it was, never half-written: a user’s old picks deleted with the
new ones not inserted, or a week’s card with some of its games missing (which
the lines job would then treat as complete, forever). It also keeps a row lock
taken inside the unit (lock_user()) until the unit ends.
A flush is enough for everything that follows inside the unit: the database
assigns ids and runs its triggers at the flush, and later reads in the same
transaction see what it wrote. Sessions do not expire on commit
(supercontest.dbsession.engine), so an object keeps what it held – and
where the database rewrote a row the ORM did not write, the function that
caused it refreshes that row before returning (_reload()).
Module Attributes
The scheduled broadcasts, as |
Functions
Adds a user to the current season's paid league, once. |
|
Changes the status of a game. |
|
Claims a broadcast for a week, unless something already has. |
|
Wrapper around deleting rows. |
|
Writes the lines and initial opponent info to the database. |
|
Row-locks a user until the transaction ends. |
|
Ends a read-only transaction, handing its connection back to the pool. |
|
Grants or revokes admin for a known user. |
|
Deletes, updates, and inserts pick objects to the database as necessary. |
|
Commits the scores to the database (insert and/or update). |
Classes
What |
- supercontest.dbsession.commits.EmailJob
The scheduled broadcasts, as
EmailSendnames them.alias of
Literal[‘picks_open’, ‘picks_closing’, ‘all_picks’]
- class supercontest.dbsession.commits.ScoresWrite(changed: bool, finished: list[Game])
Bases:
NamedTupleWhat
write_scores()did.- changed: bool
Whether anything was written, and so whether there is anything to commit.
- async supercontest.dbsession.commits.add_user_to_current_paid_league(session: AsyncSession, user: User) bool
Adds a user to the current season’s paid league, once.
Idempotent, because its main caller is Stripe’s webhook, which redelivers an event it did not see answered – and a second payment session from the same user is possible too. A repeat is a no-op, not a duplicate row: the membership table’s primary key would refuse that with an IntegrityError, turning a redelivery into a 500 that Stripe keeps retrying for days.
The caller’s commit drops the request’s memoized answers (
supercontest.dbsession.memo), so from then on the new member is seen as paid, by the rest of the request and by every request after it.- Parameters:
session – The session to write through.
user – The user to add.
- Returns:
True if they were added; False if they were already a member.
- Raises:
ValueError – Outside a season, when there is no current paid league.
- async supercontest.dbsession.commits.change_game_status(session: AsyncSession, game_id: int, status_name: str) None
Changes the status of a game.
- Parameters:
session – The session to write through.
game_id – The ID of the matchup that you’re changing the status for.
status_name – The name of the status to change to (via FK to statuses table).
- async supercontest.dbsession.commits.claim_email_send(session: AsyncSession, job: Literal['picks_open', 'picks_closing', 'all_picks'], season: int, week: int, recipients: int) bool
Claims a broadcast for a week, unless something already has.
One
INSERT ... ON CONFLICT DO NOTHINGon the table’s primary key, so two attempts at once cannot both win. The caller commits the claim before it sends, so a retry arriving mid-send already sees it.- Parameters:
session – The session to write through.
job – Which broadcast.
season – Season year.
week – Week number.
recipients – How many addresses it is going to.
- Returns:
True if this call claimed it; False if it was claimed before.
- async supercontest.dbsession.commits.delete_rows(session: AsyncSession, rows: Sequence[Base]) None
Wrapper around deleting rows. You may delete one or multiple.
- Parameters:
session – The session.
rows – The rows to delete.
- async supercontest.dbsession.commits.insert_contestants(session: AsyncSession, week_id: int, lines: list[Line]) None
Writes the lines and initial opponent info to the database. All cleaning is done before this.
- Parameters:
session – The session to write through.
week_id – The ID of the week to write lines for.
lines – The week’s lines, from the card or entered by hand.
- async supercontest.dbsession.commits.lock_user(session: AsyncSession, user_id: int) None
Row-locks a user until the transaction ends.
Serializes one user’s pick writes. The pick rules read the user’s current picks and then write, so two submissions at once – two tabs, a retried request – would each see the other’s picks missing and could together exceed the weekly limit. With the lock, the second waits for the first to commit and then reads its picks.
- Parameters:
session – The session whose transaction holds the lock.
user_id – The user whose writes to serialize.
- async supercontest.dbsession.commits.release_connection(session: AsyncSession) None
Ends a read-only transaction, handing its connection back to the pool.
For a caller about to wait on something outside the database – Westgate, ESPN, Stripe, SES. A session holds its connection from its first query until its transaction ends, idle in transaction the whole wait, and a worker’s pool is three connections: one slow fetch and one broadcast would pin most of it. The next query opens a fresh transaction; loaded objects stay usable, because sessions do not expire them on commit.
Only between reads. With a change staged it refuses, rather than commit a unit of work in the middle.
- Parameters:
session – The session.
- Raises:
RuntimeError – If the session holds unflushed changes.
- async supercontest.dbsession.commits.set_superuser(session: AsyncSession, user_email: str, is_superuser: bool = True) None
Grants or revokes admin for a known user.
Replaces adding a user to the
Adminrole, which is theis_superusercolumn now.- Parameters:
session – The session to write through.
user_email – The user to change.
is_superuser – Whether they should be an admin.
- async supercontest.dbsession.commits.write_picks(session: AsyncSession, old_pick_objs_update: list[Pick], new_pick_contestant_objs: list[Contestant], user_id: int) None
Deletes, updates, and inserts pick objects to the database as necessary.
The caller already checked which existing picks to keep. So we have a remainder to update.
If there’s the same number of existing picks and new picks, it will update them.
If there are more existing picks than new picks, it will update for the new and delete the remaining rows from existing.
If there are more new picks than existing picks, it will update the existing and then insert the remainder for the new.
All validity of the picks must be analyzed prior to this function.
- Parameters:
session – The session.
old_pick_objs_update – Existing pick objects to update.
new_pick_contestant_objs – Contestant objects for the new picks.
user_id – The ID of the user submitting these picks.
- async supercontest.dbsession.commits.write_scores(session: AsyncSession, games: list[Game], scores: list[ScoresFetch]) ScoresWrite
Commits the scores to the database (insert and/or update).
Games and scores must be in the same corresponding order.
- Parameters:
session – The session the games were loaded through, in full.
games – The return from
fetch_scores_json().scores – The return from
fetch_scores_json().
- Returns:
Whether it wrote anything, and which games it finished.
- Raises:
ValueError – If one of home/visitor doesn’t match favorite/underdog.