Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 11 additions & 27 deletions zeeguu/core/model/article_cefr_assessment.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,10 @@

from datetime import datetime
from sqlalchemy import Column, Integer, String, ForeignKey, DateTime, Enum
from sqlalchemy.exc import IntegrityError
from sqlalchemy.orm import relationship

from zeeguu.core.model.db import db
from zeeguu.core.model.race_safe import find_or_create


class ArticleCefrAssessment(db.Model):
Expand Down Expand Up @@ -79,32 +79,16 @@ def find_or_create(cls, session, article_id, commit=True):
Returns:
ArticleCefrAssessment instance
"""
assessment = session.query(cls).filter_by(article_id=article_id).first()
if not assessment:
# Two requests assessing the same article (e.g. a double tap on
# promote_to_article) can both miss the query above: it reads the
# caller's REPEATABLE READ snapshot, taken before the slow LLM call,
# so it can't see the row the other request committed meanwhile.
# The article_id primary key then lets only one insert through.
# The savepoint undoes only the losing insert, not the caller's
# pending work (see UserLanguage.find_or_create).
try:
with session.begin_nested():
assessment = cls(article_id=article_id)
session.add(assessment)
except IntegrityError:
# A locking read: a plain SELECT would reuse this
# transaction's REPEATABLE READ snapshot from the query
# above, which predates the winner's row.
assessment = (
session.query(cls)
.filter_by(article_id=article_id)
.with_for_update(read=True)
.one()
)
if commit:
session.commit()
return assessment
# Two requests assessing the same article (e.g. a double tap on
# promote_to_article) can race here; the article_id primary key lets
# one insert through and race_safe.find_or_create recovers the other.
return find_or_create(
session,
cls,
{"article_id": article_id},
lambda: cls(article_id=article_id),
commit=commit,
)

def set_llm_assessment(self, level, method):
"""
Expand Down
34 changes: 7 additions & 27 deletions zeeguu/core/model/article_fragment_context.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
from zeeguu.core.model.db import db
import sqlalchemy
from zeeguu.core.model.race_safe import find_or_create


class ArticleFragmentContext(db.Model):
Expand Down Expand Up @@ -50,34 +51,13 @@ def find_by_bookmark(cls, bookmark):

@classmethod
def find_or_create(cls, session, bookmark, article_fragment, commit=True):
existing = cls.query.filter(
cls.bookmark == bookmark,
cls.article_fragment == article_fragment,
).one_or_none()
if existing:
return existing

# Insert inside a SAVEPOINT so that if a concurrent request created the
# same (bookmark, article fragment) between our SELECT and INSERT, the
# unique constraint fires and we roll back just this insert (not the
# caller's whole transaction, which may still be uncommitted when
# commit=False) and return the row the other request created.
new = cls(
bookmark,
article_fragment,
return find_or_create(
session,
cls,
dict(bookmark=bookmark, article_fragment=article_fragment),
lambda: cls(bookmark, article_fragment),
commit=commit,
)
try:
with session.begin_nested():
session.add(new)
except sqlalchemy.exc.IntegrityError:
return cls.query.filter(
cls.bookmark == bookmark,
cls.article_fragment == article_fragment,
).one()

if commit:
session.commit()
return new

@classmethod
def get_all_user_bookmarks_for_article_fragment(
Expand Down
22 changes: 11 additions & 11 deletions zeeguu/core/model/article_public_code.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
from sqlalchemy.orm import relationship

from zeeguu.core.model.db import db
from zeeguu.core.model.race_safe import find_or_create


class ArticlePublicCode(db.Model):
Expand Down Expand Up @@ -39,18 +40,17 @@ class ArticlePublicCode(db.Model):

@classmethod
def for_article(cls, session, article_id):
existing = cls.query.filter_by(article_id=article_id).first()
if existing:
return existing
row = cls(article_id=article_id, code="".join(secrets.choice(cls.ALPHABET) for _ in range(cls.CODE_LENGTH)))
# Two readers opening a never-linked article at once race the primary
# key; the savepoint keeps the outer transaction usable, winner's code wins.
try:
with session.begin_nested():
session.add(row)
return row
except Exception:
return cls.query.filter_by(article_id=article_id).one()
# key; the winner's code wins.
return find_or_create(
session,
cls,
dict(article_id=article_id),
lambda: cls(
article_id=article_id,
code="".join(secrets.choice(cls.ALPHABET) for _ in range(cls.CODE_LENGTH)),
),
)

@classmethod
def find_article(cls, code):
Expand Down
34 changes: 7 additions & 27 deletions zeeguu/core/model/article_summary_context.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
from zeeguu.core.model.bookmark import Bookmark
from zeeguu.core.model.db import db
import sqlalchemy
from zeeguu.core.model.race_safe import find_or_create


class ArticleSummaryContext(db.Model):
Expand Down Expand Up @@ -54,34 +55,13 @@ def find_or_create(
article,
commit=True,
):
existing = cls.query.filter(
cls.bookmark == bookmark,
cls.article == article,
).one_or_none()
if existing:
return existing

# Insert inside a SAVEPOINT so that if a concurrent request created the
# same (bookmark, article) between our SELECT and INSERT, the unique
# constraint fires and we roll back just this insert (not the caller's
# whole transaction, which may still be uncommitted when commit=False) and
# return the row the other request created.
new = cls(
bookmark,
article,
return find_or_create(
session,
cls,
dict(bookmark=bookmark, article=article),
lambda: cls(bookmark, article),
commit=commit,
)
try:
with session.begin_nested():
session.add(new)
except sqlalchemy.exc.IntegrityError:
return cls.query.filter(
cls.bookmark == bookmark,
cls.article == article,
).one()

if commit:
session.commit()
return new

@classmethod
def get_all_user_bookmarks_for_article_summary(
Expand Down
34 changes: 7 additions & 27 deletions zeeguu/core/model/article_title_context.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
from zeeguu.core.model.bookmark import Bookmark
from zeeguu.core.model.db import db
import sqlalchemy
from zeeguu.core.model.race_safe import find_or_create


class ArticleTitleContext(db.Model):
Expand Down Expand Up @@ -54,34 +55,13 @@ def find_or_create(
article,
commit=True,
):
existing = cls.query.filter(
cls.bookmark == bookmark,
cls.article == article,
).one_or_none()
if existing:
return existing

# Insert inside a SAVEPOINT so that if a concurrent request created the
# same (bookmark, article) between our SELECT and INSERT, the unique
# constraint fires and we roll back just this insert (not the caller's
# whole transaction, which may still be uncommitted when commit=False) and
# return the row the other request created.
new = cls(
bookmark,
article,
return find_or_create(
session,
cls,
dict(bookmark=bookmark, article=article),
lambda: cls(bookmark, article),
commit=commit,
)
try:
with session.begin_nested():
session.add(new)
except sqlalchemy.exc.IntegrityError:
return cls.query.filter(
cls.bookmark == bookmark,
cls.article == article,
).one()

if commit:
session.commit()
return new

@classmethod
def get_all_user_bookmarks_for_article_title(
Expand Down
33 changes: 8 additions & 25 deletions zeeguu/core/model/example_sentence_context.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
from zeeguu.core.model.db import db
from zeeguu.core.model.example_sentence import ExampleSentence
import sqlalchemy
from zeeguu.core.model.race_safe import find_or_create


class ExampleSentenceContext(db.Model):
Expand Down Expand Up @@ -56,31 +57,13 @@ def find_or_create(
example_sentence,
commit=True,
):
existing = cls.query.filter(
cls.bookmark == bookmark,
cls.example_sentence == example_sentence,
).one_or_none()
if existing:
return existing

# Insert inside a SAVEPOINT so that if a concurrent request created the
# same (bookmark, example sentence) between our SELECT and INSERT, the
# unique constraint fires and we roll back just this insert (not the
# caller's whole transaction, which may still be uncommitted when
# commit=False) and return the row the other request created.
new = cls(bookmark, example_sentence)
try:
with session.begin_nested():
session.add(new)
except sqlalchemy.exc.IntegrityError:
return cls.query.filter(
cls.bookmark == bookmark,
cls.example_sentence == example_sentence,
).one()

if commit:
session.commit()
return new
return find_or_create(
session,
cls,
dict(bookmark=bookmark, example_sentence=example_sentence),
lambda: cls(bookmark, example_sentence),
commit=commit,
)

@classmethod
def get_all_user_bookmarks_for_example(
Expand Down
33 changes: 8 additions & 25 deletions zeeguu/core/model/level_adapted_article_summary_context.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
from zeeguu.core.model.db import db
import sqlalchemy
from zeeguu.core.model.race_safe import find_or_create


class LevelAdaptedArticleSummaryContext(db.Model):
Expand Down Expand Up @@ -56,31 +57,13 @@ def find_by_bookmark(cls, bookmark):

@classmethod
def find_or_create(cls, session, bookmark, level_adapted_article_text, commit=True):
existing = cls.query.filter(
cls.bookmark == bookmark,
cls.level_adapted_article_text == level_adapted_article_text,
).one_or_none()
if existing:
return existing

# Insert inside a SAVEPOINT so that if a concurrent request created the
# same (bookmark, level summary) between our SELECT and INSERT, the unique
# constraint fires and we roll back just this insert (not the caller's
# whole transaction, which may still be uncommitted when commit=False) and
# return the row the other request created.
new = cls(bookmark, level_adapted_article_text)
try:
with session.begin_nested():
session.add(new)
except sqlalchemy.exc.IntegrityError:
return cls.query.filter(
cls.bookmark == bookmark,
cls.level_adapted_article_text == level_adapted_article_text,
).one()

if commit:
session.commit()
return new
return find_or_create(
session,
cls,
dict(bookmark=bookmark, level_adapted_article_text=level_adapted_article_text),
lambda: cls(bookmark, level_adapted_article_text),
commit=commit,
)

@classmethod
def get_all_user_bookmarks_for_level_adapted_summary(
Expand Down
33 changes: 8 additions & 25 deletions zeeguu/core/model/level_adapted_article_title_context.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
from zeeguu.core.model.db import db
import sqlalchemy
from zeeguu.core.model.race_safe import find_or_create


class LevelAdaptedArticleTitleContext(db.Model):
Expand Down Expand Up @@ -58,31 +59,13 @@ def find_by_bookmark(cls, bookmark):

@classmethod
def find_or_create(cls, session, bookmark, level_adapted_article_text, commit=True):
existing = cls.query.filter(
cls.bookmark == bookmark,
cls.level_adapted_article_text == level_adapted_article_text,
).one_or_none()
if existing:
return existing

# Insert inside a SAVEPOINT so that if a concurrent request created the
# same (bookmark, level title) between our SELECT and INSERT, the unique
# constraint fires and we roll back just this insert (not the caller's
# whole transaction, which may still be uncommitted when commit=False) and
# return the row the other request created.
new = cls(bookmark, level_adapted_article_text)
try:
with session.begin_nested():
session.add(new)
except sqlalchemy.exc.IntegrityError:
return cls.query.filter(
cls.bookmark == bookmark,
cls.level_adapted_article_text == level_adapted_article_text,
).one()

if commit:
session.commit()
return new
return find_or_create(
session,
cls,
dict(bookmark=bookmark, level_adapted_article_text=level_adapted_article_text),
lambda: cls(bookmark, level_adapted_article_text),
commit=commit,
)

@classmethod
def get_all_user_bookmarks_for_level_adapted_title(
Expand Down
Loading
Loading