From 7f33937860c59ec5bfb22e08e41c956d05ae6f31 Mon Sep 17 00:00:00 2001 From: Mathieu Leplatre Date: Thu, 6 Jul 2023 12:32:21 +0200 Subject: [PATCH] Update timestamps at DB level --- ctms/crud.py | 18 +++++++----------- ctms/schemas/__init__.py | 8 +------- ctms/schemas/newsletter.py | 10 +--------- ctms/schemas/waitlist.py | 10 +--------- tests/unit/test_crud.py | 29 +++++++++++++++++++++++++++++ 5 files changed, 39 insertions(+), 36 deletions(-) diff --git a/ctms/crud.py b/ctms/crud.py index 1bdd6e85..bdc13f24 100644 --- a/ctms/crud.py +++ b/ctms/crud.py @@ -6,7 +6,7 @@ from typing import Any, Callable, Dict, List, Optional, Tuple, Type, TypeVar, cast from pydantic import UUID4 -from sqlalchemy import asc, or_ +from sqlalchemy import asc, or_, text from sqlalchemy.dialects.postgresql import insert from sqlalchemy.orm import Session, joinedload, load_only, selectinload @@ -52,8 +52,6 @@ UpdatedAddOnsInSchema, UpdatedEmailPutSchema, UpdatedFirefoxAccountsInSchema, - UpdatedNewsletterInSchema, - UpdatedWaitlistInSchema, WaitlistInSchema, ) from .schemas.base import BaseModel @@ -463,12 +461,12 @@ def create_or_update_newsletters( ) # This doesn't need to be synchronized because the next query only alters the other remaining rows. They can happen in whatever order. If you plan to change what the rest of this function does, consider changing this as well! if newsletters: - newsletters = [UpdatedNewsletterInSchema(**news.dict()) for news in newsletters] stmt = insert(Newsletter).values( [{"email_id": email_id, **n.dict()} for n in newsletters] ) stmt = stmt.on_conflict_do_update( - constraint="uix_email_name", set_=dict(stmt.excluded) + constraint="uix_email_name", + set_={**dict(stmt.excluded), "update_timestamp": text("NOW()")}, ) db.execute(stmt) @@ -487,15 +485,13 @@ def create_waitlist( def create_or_update_waitlists( db: Session, email_id: UUID4, waitlists: List[WaitlistInSchema] ): - waitlists_to_upsert = [ - UpdatedWaitlistInSchema(**waitlist.dict()) for waitlist in waitlists - ] - if waitlists_to_upsert: + if waitlists: stmt = insert(Waitlist).values( - [{"email_id": email_id, **wl.dict()} for wl in waitlists_to_upsert] + [{"email_id": email_id, **wl.dict()} for wl in waitlists] ) stmt = stmt.on_conflict_do_update( - constraint="uix_wl_email_name", set_=dict(stmt.excluded) + constraint="uix_wl_email_name", + set_={**dict(stmt.excluded), "update_timestamp": text("NOW()")}, ) db.execute(stmt) diff --git a/ctms/schemas/__init__.py b/ctms/schemas/__init__.py index 76b4a3c5..760b703a 100644 --- a/ctms/schemas/__init__.py +++ b/ctms/schemas/__init__.py @@ -24,12 +24,7 @@ UpdatedFirefoxAccountsInSchema, ) from .mofo import MozillaFoundationInSchema, MozillaFoundationSchema -from .newsletter import ( - NewsletterInSchema, - NewsletterSchema, - NewsletterTableSchema, - UpdatedNewsletterInSchema, -) +from .newsletter import NewsletterInSchema, NewsletterSchema, NewsletterTableSchema from .product import ProductBaseSchema from .stripe_customer import ( StripeCustomerCreateSchema, @@ -69,7 +64,6 @@ ) from .waitlist import ( RelayWaitlistInSchema, - UpdatedWaitlistInSchema, VpnWaitlistInSchema, WaitlistInSchema, WaitlistSchema, diff --git a/ctms/schemas/newsletter.py b/ctms/schemas/newsletter.py index 54f6b151..85308a37 100644 --- a/ctms/schemas/newsletter.py +++ b/ctms/schemas/newsletter.py @@ -1,4 +1,4 @@ -from datetime import datetime, timezone +from datetime import datetime from typing import TYPE_CHECKING, Literal, Optional from pydantic import UUID4, AnyUrl, Field @@ -66,11 +66,3 @@ class NewsletterTableSchema(NewsletterBase): class Config: extra = "forbid" - - -class UpdatedNewsletterInSchema(NewsletterInSchema): - update_timestamp: datetime = Field( - default_factory=lambda: datetime.now(timezone.utc), - description="Newsletter subscription data update timestamp", - example="2021-01-28T21:26:57.511Z", - ) diff --git a/ctms/schemas/waitlist.py b/ctms/schemas/waitlist.py index 61e5500d..b9b3af1a 100644 --- a/ctms/schemas/waitlist.py +++ b/ctms/schemas/waitlist.py @@ -1,4 +1,4 @@ -from datetime import datetime, timezone +from datetime import datetime from typing import Optional from pydantic import UUID4, AnyUrl, Field, root_validator @@ -62,14 +62,6 @@ class Config: WaitlistInSchema = WaitlistBase -class UpdatedWaitlistInSchema(WaitlistInSchema): - update_timestamp: datetime = Field( - default_factory=lambda: datetime.now(timezone.utc), - description="Waitlist data update timestamp", - example="2021-01-28T21:26:57.511Z", - ) - - class WaitlistTableSchema(WaitlistBase): email_id: UUID4 = Field( description=EMAIL_ID_DESCRIPTION, diff --git a/tests/unit/test_crud.py b/tests/unit/test_crud.py index d7f4d9cb..fd779134 100644 --- a/tests/unit/test_crud.py +++ b/tests/unit/test_crud.py @@ -15,6 +15,7 @@ create_fxa, create_mofo, create_newsletter, + create_or_update_contact, delete_acoustic_field, delete_acoustic_newsletters_mapping, delete_acoustic_record, @@ -38,6 +39,8 @@ MozillaFoundationInSchema, NewsletterInSchema, ) +from ctms.schemas.contact import ContactPutSchema +from ctms.schemas.waitlist import WaitlistInSchema # Treat all SQLAlchemy warnings as errors pytestmark = pytest.mark.filterwarnings("error::sqlalchemy.exc.SAWarning") @@ -810,6 +813,32 @@ def test_relations_on_stripe_subscription_items( assert subscription_item.get_email_id() == email.email_id +def test_create_or_update_contact_timestamps(dbsession, email_factory): + email = email_factory( + newsletters=1, + waitlists=1, + ) + dbsession.flush() + + before_nl = email.newsletters[0].update_timestamp + before_wl = email.waitlists[0].update_timestamp + + new_source = "http://waitlists.example.com" + putdata = ContactPutSchema( + email=EmailInSchema(email_id=email.email_id, primary_email=email.primary_email), + newsletters=[ + NewsletterInSchema(name=email.newsletters[0].name, source=new_source) + ], + waitlists=[WaitlistInSchema(name=email.waitlists[0].name, source=new_source)], + ) + create_or_update_contact(dbsession, email.email_id, putdata, None) + dbsession.commit() + + updated_email = get_email(dbsession, email.email_id) + assert updated_email.newsletters[0].update_timestamp != before_nl + assert updated_email.waitlists[0].update_timestamp != before_wl + + def test_get_contacts_from_newsletter(dbsession, newsletter_factory): existing_newsletter = newsletter_factory() dbsession.flush()