コード例 #1
0
def test_inactive_challenge(app):
    setup_challenges(app)
    with app.app_context():
        db = get_db()

    redis_conn = redis.Redis.from_url(url=REDIS_URL)

    bus = ChallengeEventBus(redis_conn)
    with db.scoped_session() as session:
        mgr = ChallengeManager("some_inactive_challenge", DefaultUpdater())
        TEST_EVENT = "TEST_EVENT"
        bus.register_listener(TEST_EVENT, mgr)
        with bus.use_scoped_dispatch_queue():
            bus.dispatch(TEST_EVENT, 100, 1, {})
        bus.process_events(session)
        state = mgr.get_user_challenge_state(session, ["1"])
        # We should not have any UserChallenges created for the
        # inactive challenge!!
        assert len(state) == 0
コード例 #2
0
def test_rejects_invalid_events(app):
    setup_challenges(app)
    with app.app_context():
        db = get_db()

    redis_conn = redis.Redis.from_url(url=REDIS_URL)

    bus = ChallengeEventBus(redis_conn)
    with db.scoped_session() as session:
        mgr = ChallengeManager("test_challenge_1", DefaultUpdater())
        TEST_EVENT = "TEST_EVENT"
        bus.register_listener(TEST_EVENT, mgr)
        with bus.use_scoped_dispatch_queue():
            bus.dispatch(TEST_EVENT, None, 1)
            bus.dispatch(TEST_EVENT, 1, None)
            bus.dispatch(TEST_EVENT, 1, 1, 1)
        (count, did_error) = bus.process_events(session)
        assert count == 0
        assert did_error == False
コード例 #3
0
def test_in_memory_queue(app):
    setup_challenges(app)

    with app.app_context():
        db = get_db()

    redis_conn = redis.Redis.from_url(url=REDIS_URL)

    bus = ChallengeEventBus(redis_conn)
    with db.scoped_session() as session, bus.use_scoped_dispatch_queue():
        agg_challenge = ChallengeManager("test_challenge_3",
                                         AggregateUpdater())
        agg_challenge.process(session, "test_event", [])
        TEST_EVENT = "TEST_EVENT"

        bus.register_listener(
            TEST_EVENT, ChallengeManager("test_challenge_1", DefaultUpdater()))
        bus.register_listener(
            TEST_EVENT, ChallengeManager("test_challenge_2", DefaultUpdater()))
        # - Multiple events with the same user_id but diff specifiers get created
        bus.register_listener(TEST_EVENT, agg_challenge)
        bus.dispatch(TEST_EVENT, 100, 1, {"referred_id": 2})
        bus.dispatch(TEST_EVENT, 100, 1, {"referred_id": 3})
        bus.process_events(session)

        # no events should be processed because we haven't dispatched yet
        state = agg_challenge.get_user_challenge_state(session, ["1-2", "1-3"])
        assert len(state) == 0

    bus.process_events(session)
    state = agg_challenge.get_user_challenge_state(session, ["1-2", "1-3"])
    assert len(state) == 2
    # Also make sure the thing is incomplete
    res = get_challenges(1, False, session, bus)
    agg_chal = {c["challenge_id"]: c for c in res}["test_challenge_3"]
    assert agg_chal["is_complete"] == False

    redis_conn = redis.Redis.from_url(url=REDIS_URL)
コード例 #4
0
        ).all())
        follows_counter = Counter(map(lambda x: x.user_id, favorites))
        for completion in partial_completions:
            completion.favorites = (follows_counter[completion.user_id] >=
                                    FAVORITES_THRESHOLD)

    def _get_steps_complete(self, partial_challenge):
        return (partial_challenge.profile_description +
                partial_challenge.profile_name +
                partial_challenge.profile_picture +
                partial_challenge.profile_cover_photo +
                partial_challenge.follows + partial_challenge.favorites +
                partial_challenge.reposts)


profile_challenge_manager = ChallengeManager("profile-completion",
                                             ProfileChallengeUpdater())


# Accessors
def get_profile_completion_challenges(session, user_ids):
    return (session.query(ProfileCompletionChallenge).filter(
        ProfileCompletionChallenge.user_id.in_(user_ids)).all())


def get_user_dicts(session, user_ids):
    res = (session.query(
        User.bio,
        User.name,
        User.profile_picture,
        User.profile_picture_sizes,
        User.cover_photo,
コード例 #5
0
from typing import List, Optional

from sqlalchemy.orm.session import Session
from src.challenges.challenge import (
    ChallengeManager,
    ChallengeUpdater,
    FullEventMetadata,
    UserChallenge,
)


class MobileInstallChallengeUpdater(ChallengeUpdater):
    def update_user_challenges(
        self,
        session: Session,
        event: str,
        user_challenges: List[UserChallenge],
        step_count: Optional[int],
        event_metadatas: List[FullEventMetadata],
        starting_block: Optional[int],
    ):
        # We only fire the event if the user logged in on mobile
        for user_challenge in user_challenges:
            user_challenge.is_complete = True


mobile_install_challenge_manager = ChallengeManager(
    "mobile-install", MobileInstallChallengeUpdater())
コード例 #6
0
    def should_create_new_challenge(self, session, event: str, user_id: int,
                                    extra: Dict) -> bool:
        return does_user_exist_with_verification_status(session, user_id, True)

    def should_show_challenge_for_user(self, session: Session,
                                       user_id: int) -> bool:
        return does_user_exist_with_verification_status(session, user_id, True)


class ReferredChallengeUpdater(ChallengeUpdater):
    def update_user_challenges(
        self,
        session: Session,
        event: str,
        user_challenges: List[UserChallenge],
        step_count: Optional[int],
        event_metadatas: List[FullEventMetadata],
        starting_block: Optional[int],
    ):
        for user_challenge in user_challenges:
            user_challenge.is_complete = True


referral_challenge_manager = ChallengeManager("referrals",
                                              ReferralChallengeUpdater())
verified_referral_challenge_manager = ChallengeManager(
    "ref-v", VerifiedReferralChallengeUpdater())

referred_challenge_manager = ChallengeManager("referred",
                                              ReferredChallengeUpdater())
コード例 #7
0
from typing import List, Optional

from sqlalchemy.orm.session import Session
from src.challenges.challenge import (
    ChallengeManager,
    ChallengeUpdater,
    FullEventMetadata,
)
from src.models.models import UserChallenge


class SendFirstTipChallengeUpdater(ChallengeUpdater):
    def update_user_challenges(
        self,
        session: Session,
        event: str,
        user_challenges: List[UserChallenge],
        step_count: Optional[int],
        event_metadatas: List[FullEventMetadata],
        starting_block: Optional[int],
    ):
        # Update the user_challenges
        for user_challenge in user_challenges:
            # Update completion
            user_challenge.is_complete = True


send_first_tip_challenge_manager = ChallengeManager(
    "send-first-tip", SendFirstTipChallengeUpdater())
コード例 #8
0
def test_catches_exceptions_in_single_processor(app):
    """Ensure that if a single processor fails, the others still succeed"""
    with app.app_context():
        db = get_db()

    redis_conn = redis.Redis.from_url(url=REDIS_URL)

    bus = ChallengeEventBus(redis_conn)
    with db.scoped_session() as session:

        session.add_all([
            Challenge(
                id="test_challenge_1",
                type=ChallengeType.numeric,
                amount="5",
                step_count=3,
                active=True,
            ),
            Challenge(
                id="test_challenge_2",
                type=ChallengeType.numeric,
                amount="5",
                step_count=3,
                active=True,
            ),
        ])
        session.commit()

        correct_manager = ChallengeManager("test_challenge_1",
                                           DefaultUpdater())
        broken_manager = ChallengeManager("test_challenge_2", BrokenUpdater())
        TEST_EVENT = "TEST_EVENT"
        TEST_EVENT_2 = "TEST_EVENT_2"
        bus.register_listener(TEST_EVENT, correct_manager)
        bus.register_listener(TEST_EVENT_2, broken_manager)

        with bus.use_scoped_dispatch_queue():
            # dispatch the broken one first
            bus.dispatch(TEST_EVENT_2, 101, 1)
            bus.dispatch(TEST_EVENT, 101, 1)
        try:
            bus.process_events(session)
        except:
            # pylint: disable=W0707
            raise Exception("Shouldn't have propogated error!")
        challenge_1_state = correct_manager.get_user_challenge_state(
            session, ["1"])
        # Make sure that the 'correct_manager' still executes
        assert len(challenge_1_state) == 1
        assert challenge_1_state[0].current_step_count == 1
        # Make sure broken manager didn't do anything
        challenge_2_state = broken_manager.get_user_challenge_state(
            session, ["1"])
        assert len(challenge_2_state) == 0

        # Try the other order
        with bus.use_scoped_dispatch_queue():
            # dispatch the correct one first
            bus.dispatch(TEST_EVENT, 101, 1)
            bus.dispatch(TEST_EVENT_2, 101, 1)
        try:
            bus.process_events(session)
        except:
            # pylint: disable=W0707
            raise Exception("Shouldn't have propogated error!")
        challenge_1_state = correct_manager.get_user_challenge_state(
            session, ["1"])
        assert len(challenge_1_state) == 1
        assert challenge_1_state[0].current_step_count == 2
        # Make sure broken manager didn't do anything
        challenge_2_state = broken_manager.get_user_challenge_state(
            session, ["1"])
        assert len(challenge_2_state) == 0
コード例 #9
0
def test_aggregates(app):

    setup_challenges(app)

    with app.app_context():
        db = get_db()

    redis_conn = redis.Redis.from_url(url=REDIS_URL)

    with db.scoped_session() as session:
        bus = ChallengeEventBus(redis_conn)
        agg_challenge = ChallengeManager("test_challenge_3",
                                         AggregateUpdater())
        agg_challenge.process(session, "test_event", [])
        TEST_EVENT = "TEST_EVENT"

        bus.register_listener(
            TEST_EVENT, ChallengeManager("test_challenge_1", DefaultUpdater()))
        bus.register_listener(
            TEST_EVENT, ChallengeManager("test_challenge_2", DefaultUpdater()))
        # - Multiple events with the same user_id but diff specifiers get created
        bus.register_listener(TEST_EVENT, agg_challenge)
        bus.dispatch(TEST_EVENT, 100, 1, {"referred_id": 2})
        bus.dispatch(TEST_EVENT, 100, 1, {"referred_id": 3})
        bus.flush()
        bus.process_events(session)
        state = agg_challenge.get_user_challenge_state(session, ["1-2", "1-3"])
        assert len(state) == 2
        # Also make sure the thing is incomplete
        res = get_challenges(1, False, session, bus)
        agg_chal = {c["challenge_id"]: c for c in res}["test_challenge_3"]
        assert agg_chal["is_complete"] == False

        # - Multiple events with the same specifier get deduped
        bus.dispatch(TEST_EVENT, 100, 1, {"referred_id": 4})
        bus.dispatch(TEST_EVENT, 100, 1, {"referred_id": 4})
        bus.flush()
        bus.process_events(session)
        state = agg_challenge.get_user_challenge_state(session, ["1-4"])
        assert len(state) == 1

        # - If we've maxed the # of challenges, don't create any more
        bus.dispatch(TEST_EVENT, 100, 1, {"referred_id": 5})
        bus.dispatch(TEST_EVENT, 100, 1, {"referred_id": 6})
        bus.flush()
        bus.process_events(session)

        def get_user_challenges():
            return (session.query(UserChallenge).filter(
                UserChallenge.challenge_id == "test_challenge_3",
                UserChallenge.user_id == 1,
            ).all())

        assert len(get_user_challenges()) == 5
        bus.dispatch(TEST_EVENT, 100, 1, {"referred_id": 7})
        bus.flush()
        bus.process_events(session)
        assert len(get_user_challenges()) == 5

        # Test get_challenges
        res = get_challenges(1, False, session, bus)
        agg_chal = {c["challenge_id"]: c for c in res}["test_challenge_3"]
        assert agg_chal["is_complete"] == True
        # Assert all user challenges have proper finishing block #
        user_challenges = get_user_challenges()
        for uc in user_challenges:
            assert uc.completed_blocknumber == 100
コード例 #10
0
from typing import List, Optional

from sqlalchemy.orm.session import Session
from src.challenges.challenge import (
    ChallengeManager,
    ChallengeUpdater,
    FullEventMetadata,
)
from src.models.models import UserChallenge


class ConnectVerifiedChallengeUpdater(ChallengeUpdater):
    """Updates a connect verified challenge."""
    def update_user_challenges(
        self,
        session: Session,
        event: str,
        user_challenges: List[UserChallenge],
        step_count: Optional[int],
        event_metadatas: List[FullEventMetadata],
        starting_block: Optional[int],
    ):
        # Update the user_challenges
        for user_challenge in user_challenges:
            # Update completion
            user_challenge.is_complete = True


connect_verified_challenge_manager = ChallengeManager(
    "connect-verified", ConnectVerifiedChallengeUpdater())
コード例 #11
0
def test_handle_event(app):
    setup_challenges(app)

    with app.app_context():
        db = get_db()

    populate_mock_db_blocks(db, 99, 110)

    with db.scoped_session() as session:

        my_challenge = ChallengeManager("test_challenge_1", TestUpdater())
        # First try an event with a insufficient block_number
        # to ensure that nothing happens
        my_challenge.process(
            session,
            "test_event",
            [
                {
                    "user_id": 1,
                    "block_number": 99,
                    "extra": {}
                },
            ],
        )
        session.flush()
        actual = (session.query(UserChallenge).filter(
            UserChallenge.challenge_id == "test_challenge_1",
            UserChallenge.user_id == 1,
        ).first())
        expected = {
            "challenge_id": "test_challenge_1",
            "user_id": 1,
            "specifier": "1",
            "is_complete": False,
            "current_step_count": 1,
            "completed_blocknumber": None,
        }
        assert model_to_dictionary(actual) == expected

        # Now process events and make sure things change as expected
        my_challenge.process(
            session,
            "test_event",
            [
                {
                    "user_id": 1,
                    "block_number": 100,
                    "extra": {}
                },
                {
                    "user_id": 2,
                    "block_number": 100,
                    "extra": {}
                },
                {
                    "user_id": 3,
                    "block_number": 100,
                    "extra": {}
                },
                # Attempt to add id 6 twice to
                # ensure that it doesn't cause a collision
                {
                    "user_id": 6,
                    "block_number": 100,
                    "extra": {}
                },
                {
                    "user_id": 6,
                    "block_number": 100,
                    "extra": {}
                },
            ],
        )
        session.flush()

        updated_complete = (session.query(UserChallenge).filter(
            UserChallenge.challenge_id == "test_challenge_1").all())
        res_dicts = list(map(model_to_dictionary, updated_complete))
        expected = [
            # Should have incremented step count + 1
            {
                "challenge_id": "test_challenge_1",
                "user_id": 1,
                "specifier": "1",
                "is_complete": False,
                "current_step_count": 2,
                "completed_blocknumber": None,
            },
            # Should be unchanged b/c it was already complete
            {
                "challenge_id": "test_challenge_1",
                "user_id": 2,
                "specifier": "2",
                "is_complete": True,
                "current_step_count": 3,
                "completed_blocknumber": 100,
            },
            # Should be newly complete
            {
                "challenge_id": "test_challenge_1",
                "user_id": 3,
                "specifier": "3",
                "is_complete": True,
                "current_step_count": 3,
                "completed_blocknumber": 100,
            },
            # Should be untouched bc user 5 wasn't included
            {
                "challenge_id": "test_challenge_1",
                "user_id": 5,
                "specifier": "5",
                "is_complete": False,
                "current_step_count": 2,
                "completed_blocknumber": None,
            },
            # Should have created a brand new user 6
            {
                "challenge_id": "test_challenge_1",
                "user_id": 6,
                "specifier": "6",
                "is_complete": False,
                "current_step_count": 1,
                "completed_blocknumber": None,
            },
        ]
        assert expected == res_dicts
コード例 #12
0
def setup_db(session):
    blocks = [Block(blockhash="0x1", number=1, parenthash="", is_current=True)]
    users = [
        User(
            blockhash="0x1",
            blocknumber=1,
            user_id=1,
            is_current=True,
            wallet="0x38C68fF3926bf4E68289672F75ee1543117dD9B3",
            created_at=datetime.now(),
            updated_at=datetime.now(),
        )
    ]
    challenges = [
        Challenge(
            id="boolean_challenge_1",
            type=ChallengeType.boolean,
            active=True,
            amount="5",
        ),
        Challenge(
            id="boolean_challenge_2",
            type=ChallengeType.boolean,
            active=True,
            amount="5",
        ),
        Challenge(
            id="boolean_challenge_3",
            type=ChallengeType.boolean,
            active=True,
            amount="5",
        ),
        # No progress on this, but active
        # should be returned
        Challenge(
            id="boolean_challenge_4",
            type=ChallengeType.boolean,
            active=True,
            amount="5",
        ),
        # Inactive, with no progress
        Challenge(
            id="boolean_challenge_5",
            type=ChallengeType.boolean,
            active=False,
            amount="5",
        ),
        # Inactive, WITH progress
        Challenge(
            id="boolean_challenge_6",
            type=ChallengeType.boolean,
            active=False,
            amount="5",
        ),
        Challenge(
            id="trending_challenge_1",
            type=ChallengeType.trending,
            active=True,
            amount="5",
        ),
        Challenge(
            id="aggregate_challenge_1",
            type=ChallengeType.aggregate,
            active=True,
            amount="5",
            step_count=3,
        ),
        Challenge(
            id="aggregate_challenge_2",
            type=ChallengeType.aggregate,
            active=True,
            amount="5",
            step_count=2,
        ),
        Challenge(
            id="aggregate_challenge_3",
            type=ChallengeType.aggregate,
            active=True,
            amount="5",
            step_count=2,
        ),
        Challenge(id="trending_1",
                  type=ChallengeType.trending,
                  active=True,
                  amount="5"),
        Challenge(id="trending_2",
                  type=ChallengeType.trending,
                  active=True,
                  amount="5"),
        Challenge(id="trending_3",
                  type=ChallengeType.trending,
                  active=True,
                  amount="5"),
    ]
    user_challenges = [
        # Finished the first challenge, disbursed
        UserChallenge(
            challenge_id="boolean_challenge_1",
            user_id=1,
            specifier="1",
            is_complete=True,
        ),
        # Did finish the second challenge, did not disburse
        UserChallenge(
            challenge_id="boolean_challenge_2",
            user_id=1,
            specifier="1",
            is_complete=True,
        ),
        # Did not finish challenge 3
        UserChallenge(
            challenge_id="boolean_challenge_3",
            user_id=1,
            specifier="1",
            is_complete=False,
        ),
        # Inactive challenge
        UserChallenge(
            challenge_id="boolean_challenge_6",
            user_id=1,
            specifier="1",
            is_complete=True,
        ),
        UserChallenge(
            challenge_id="aggregate_challenge_1",
            user_id=1,
            specifier="1-2",  # compound specifiers, like if user1 invites user2
            is_complete=True,
        ),
        # Ensure that a non-complete user-challenge isn't counted towards
        # aggregate challenge score
        UserChallenge(
            challenge_id="aggregate_challenge_1",
            user_id=1,
            specifier="1-3",
            is_complete=False,
        ),
        UserChallenge(
            challenge_id="aggregate_challenge_2",
            user_id=1,
            specifier="1-2",
            is_complete=True,
        ),
        UserChallenge(
            challenge_id="aggregate_challenge_2",
            user_id=1,
            specifier="1-3",
            is_complete=True,
        ),
        # Trending 1 should be finished and included
        UserChallenge(
            challenge_id="trending_1",
            user_id=1,
            specifier="06-01-2020",
            is_complete=True,
        ),
        # Trending 2 should not be included
        UserChallenge(
            challenge_id="trending_2",
            user_id=1,
            specifier="06-01-2020",
            is_complete=False,
        ),
    ]
    disbursements = [
        ChallengeDisbursement(
            challenge_id="boolean_challenge_1",
            user_id=1,
            amount="5",
            signature="1",
            slot=1,
            specifier="1",
        )
    ]

    # Wipe any existing challenges in the DB from running migrations, etc
    session.query(Challenge).delete()
    session.commit()
    session.add_all(blocks)
    session.commit()
    session.add_all(users)
    session.commit()
    session.add_all(challenges)
    session.commit()
    session.add_all(user_challenges)
    session.commit()
    session.add_all(disbursements)

    redis_conn = redis.Redis.from_url(url=REDIS_URL)
    bus = ChallengeEventBus(redis_conn)
    challenge_types = [
        "boolean_challenge_1",
        "boolean_challenge_2",
        "boolean_challenge_3",
        "boolean_challenge_4",
        "boolean_challenge_5",
        "boolean_challenge_6",
        "trending_challenge_1",
        "aggregate_challenge_1",
        "aggregate_challenge_2",
        "aggregate_challenge_3",
        "trending_1",
        "trending_2",
        "trending_3",
    ]
    for ct in challenge_types:
        bus.register_listener(
            DEFAULT_EVENT,
            ChallengeManager(ct, DefaultUpdater()),
        )
    return bus
コード例 #13
0
def setup_extra_metadata_test(session):
    blocks = [Block(blockhash="0x1", number=1, parenthash="", is_current=True)]
    users = [
        User(
            blockhash="0x1",
            blocknumber=1,
            user_id=1,
            is_current=True,
            wallet="0x38C68fF3926bf4E68289672F75ee1543117dD9B3",
            created_at=datetime.now(),
            updated_at=datetime.now(),
        )
    ]
    challenges = [
        # Test numeric challenges
        # Numeric 1 with default extra data, no completion
        Challenge(
            id="numeric_1",
            type=ChallengeType.numeric,
            active=True,
            amount="5",
            step_count=5,
        ),
        # Numeric 2 with some extra data
        Challenge(
            id="numeric_2",
            type=ChallengeType.numeric,
            active=True,
            amount="5",
            step_count=5,
        ),
    ]

    user_challenges = [
        UserChallenge(
            challenge_id="numeric_2",
            user_id=1,
            specifier="1",
            is_complete=False,
            current_step_count=5,
        ),
    ]

    session.query(Challenge).delete()
    session.commit()
    session.add_all(blocks)
    session.commit()
    session.add_all(users)
    session.commit()
    session.add_all(challenges)
    session.commit()
    session.add_all(user_challenges)
    session.commit()

    redis_conn = redis.Redis.from_url(url=REDIS_URL)
    bus = ChallengeEventBus(redis_conn)
    bus.register_listener(
        DEFAULT_EVENT, ChallengeManager("numeric_1", NumericCustomUpdater()))
    bus.register_listener(
        DEFAULT_EVENT, ChallengeManager("numeric_2", NumericCustomUpdater()))
    return bus
コード例 #14
0
        # Update the user_challenges
        for user_challenge in user_challenges:
            # Update step count
            user_challenge.current_step_count = num_tracks_per_user[
                user_challenge.user_id]
            # Update completion
            user_challenge.is_complete = (
                user_challenge.current_step_count is not None
                and user_challenge.current_step_count >= step_count)

    def _get_num_track_uploads_by_user(self, session: Session,
                                       user_challenges: List[UserChallenge],
                                       block_number: int):
        user_ids = [
            user_challenge.user_id for user_challenge in user_challenges
        ]
        tracks = (session.query(Track).filter(
            Track.owner_id.in_(user_ids),
            Track.blocknumber >= block_number,
            Track.is_current == True,
            Track.is_delete == False,
            Track.is_unlisted == False,
            Track.stem_of == None,
        ).all())
        num_tracks_per_user = Counter(map(lambda t: t.owner_id, tracks))
        return num_tracks_per_user


track_upload_challenge_manager = ChallengeManager(
    "track-upload", TrackUploadChallengeUpdater())
コード例 #15
0
                partial_completion.listen_streak = 1
            # If last timestamp is more than 24 hours ago, update streak
            elif new_date - last_date >= timedelta(days=1):
                partial_completion.last_listen_date = new_date
                # Check if the user lost their streak
                if new_date - last_date >= timedelta(days=2):
                    partial_completion.listen_streak = 1
                else:
                    partial_completion.listen_streak += 1

    def get_override_challenge_step_count(
        self, session: Session, user_id: int
    ) -> Optional[int]:
        return get_listen_streak_override(session, user_id)


listen_streak_challenge_manager = ChallengeManager(
    "listen-streak", ListenStreakChallengeUpdater()
)


# Accessors
def get_listen_streak_challenges(
    session: Session, user_ids: List[int]
) -> List[ListenStreakChallenge]:
    return (
        session.query(ListenStreakChallenge)
        .filter(ListenStreakChallenge.user_id.in_(user_ids))
        .all()
    )
コード例 #16
0
                user_id=metadata["extra"]["user_id"],
                id=metadata["extra"]["id"],
                rank=metadata["extra"]["rank"],
                type=metadata["extra"]["type"],
                version=metadata["extra"]["version"],
                week=metadata["extra"]["week"],
            )
            for metadata in metadatas
        ]
        session.add_all(trending_results)

    def generate_specifier(self, user_id: int, extra: Dict) -> str:
        return f"{extra['week']}:{extra['rank']}"


trending_track_challenge_manager = ChallengeManager("tt", TrendingChallengeUpdater())

trending_underground_track_challenge_manager = ChallengeManager(
    "tut", TrendingChallengeUpdater()
)

trending_playlist_challenge_manager = ChallengeManager("tp", TrendingChallengeUpdater())


def is_dst(zonename, dt):
    """Checks if is daylight savings time
    During daylight savings, the clock moves forward one hr
    """
    tz = pytz.timezone(zonename)
    localized = pytz.utc.localize(dt)
    return localized.astimezone(tz).dst() != timedelta(0)