feat(backend): Add celery beat schedule
							parent
							
								
									13fd7fdfc0
								
							
						
					
					
						commit
						1d6dce5088
					
				| 
						 | 
					@ -4,6 +4,7 @@ from flask_migrate import Migrate
 | 
				
			||||||
from flask_sqlalchemy import SQLAlchemy
 | 
					from flask_sqlalchemy import SQLAlchemy
 | 
				
			||||||
from sqlalchemy import MetaData
 | 
					from sqlalchemy import MetaData
 | 
				
			||||||
from sqlalchemy.orm import DeclarativeBase
 | 
					from sqlalchemy.orm import DeclarativeBase
 | 
				
			||||||
 | 
					from celery import Celery, Task
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class BaseModel(DeclarativeBase):
 | 
					class BaseModel(DeclarativeBase):
 | 
				
			||||||
    pass
 | 
					    pass
 | 
				
			||||||
| 
						 | 
					@ -33,9 +34,11 @@ def connect_db_with_app(database_uri: str | None, include_migrate=True):
 | 
				
			||||||
        print("Creating tables if they do not exist")
 | 
					        print("Creating tables if they do not exist")
 | 
				
			||||||
        db.create_all()
 | 
					        db.create_all()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def connect_celery_with_app():
 | 
					def connect_celery_with_app() -> Celery:
 | 
				
			||||||
 | 
					    if "celery" in app.extensions:
 | 
				
			||||||
 | 
					        return app.extensions["celery"]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def celery_init_app(app):
 | 
					    def celery_init_app(app):
 | 
				
			||||||
        from celery import Celery, Task
 | 
					 | 
				
			||||||
        class FlaskTask(Task):
 | 
					        class FlaskTask(Task):
 | 
				
			||||||
            def __call__(self, *args: object, **kwargs: object) -> object:
 | 
					            def __call__(self, *args: object, **kwargs: object) -> object:
 | 
				
			||||||
                with app.app_context():
 | 
					                with app.app_context():
 | 
				
			||||||
| 
						 | 
					@ -55,7 +58,7 @@ def connect_celery_with_app():
 | 
				
			||||||
        )
 | 
					        )
 | 
				
			||||||
    )
 | 
					    )
 | 
				
			||||||
    app.config.from_prefixed_env()
 | 
					    app.config.from_prefixed_env()
 | 
				
			||||||
    celery_init_app(app)
 | 
					    return celery_init_app(app)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def create_app() -> Flask:
 | 
					def create_app() -> Flask:
 | 
				
			||||||
    return Flask(__name__)
 | 
					    return Flask(__name__)
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -14,6 +14,11 @@ from models.player_team import PlayerTeam
 | 
				
			||||||
from models.team_integration import TeamLogsTfIntegration
 | 
					from models.team_integration import TeamLogsTfIntegration
 | 
				
			||||||
from celery import shared_task
 | 
					from celery import shared_task
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					celery = app_db.connect_celery_with_app()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@celery.on_after_configure.connect
 | 
				
			||||||
 | 
					def setup_periodic_task(sender, **kwargs):
 | 
				
			||||||
 | 
					    sender.add_periodic_task(30.0, etl_periodic.s(), name="Fetch logs every 5 minutes")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
FETCH_URL = "https://logs.tf/api/v1/log/{}"
 | 
					FETCH_URL = "https://logs.tf/api/v1/log/{}"
 | 
				
			||||||
SEARCH_URL = "https://logs.tf/api/v1/log?limit=25?offset={}"
 | 
					SEARCH_URL = "https://logs.tf/api/v1/log?limit=25?offset={}"
 | 
				
			||||||
| 
						 | 
					@ -26,10 +31,13 @@ def get_log_ids(last_log_id: int):
 | 
				
			||||||
        for summary in response.json()["logs"]:
 | 
					        for summary in response.json()["logs"]:
 | 
				
			||||||
            id: int = summary["id"]
 | 
					            id: int = summary["id"]
 | 
				
			||||||
            if id == last_log_id:
 | 
					            if id == last_log_id:
 | 
				
			||||||
                break
 | 
					                print("Reached last log ID", id)
 | 
				
			||||||
 | 
					                return
 | 
				
			||||||
            # yield models.match.RawLogSummary.from_response(summary)
 | 
					            # yield models.match.RawLogSummary.from_response(summary)
 | 
				
			||||||
            yield id
 | 
					            yield id
 | 
				
			||||||
            current = id
 | 
					            current = id
 | 
				
			||||||
 | 
					        sleep(5)
 | 
				
			||||||
 | 
					        break
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def extract(log_id: int) -> models.match.RawLogDetails:
 | 
					def extract(log_id: int) -> models.match.RawLogDetails:
 | 
				
			||||||
    response = requests.get(FETCH_URL.format(log_id))
 | 
					    response = requests.get(FETCH_URL.format(log_id))
 | 
				
			||||||
| 
						 | 
					@ -68,20 +76,6 @@ def extract_steam_ids(players: dict[str, models.match.LogPlayer]):
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@shared_task
 | 
					@shared_task
 | 
				
			||||||
def update_playtime(steam_ids: list[int]):
 | 
					def update_playtime(steam_ids: list[int]):
 | 
				
			||||||
    # update players with playtime (recalculate through aggregation)
 | 
					 | 
				
			||||||
    #subquery = (
 | 
					 | 
				
			||||||
    #    app_db.db.session.query(
 | 
					 | 
				
			||||||
    #        PlayerTeam.id,
 | 
					 | 
				
			||||||
    #        func.sum(Match.duration).label("total_playtime")
 | 
					 | 
				
			||||||
    #    )
 | 
					 | 
				
			||||||
    #    .join(PlayerMatch, PlayerMatch.player_id == PlayerTeam.player_id)
 | 
					 | 
				
			||||||
    #    .join(Match, PlayerMatch.match_id == Match.logs_tf_id)
 | 
					 | 
				
			||||||
    #    .join(TeamMatch, TeamMatch.match_id == Match.logs_tf_id)
 | 
					 | 
				
			||||||
    #    .where(PlayerTeam.player_id.in_(steam_ids))
 | 
					 | 
				
			||||||
    #    .where(PlayerTeam.team_id == TeamMatch.team_id)
 | 
					 | 
				
			||||||
    #    .group_by(PlayerTeam.id)
 | 
					 | 
				
			||||||
    #    .subquery()
 | 
					 | 
				
			||||||
    #)
 | 
					 | 
				
			||||||
    steam_ids_int = list(map(lambda x: int(x), steam_ids))
 | 
					    steam_ids_int = list(map(lambda x: int(x), steam_ids))
 | 
				
			||||||
    ptp = (
 | 
					    ptp = (
 | 
				
			||||||
        select(
 | 
					        select(
 | 
				
			||||||
| 
						 | 
					@ -154,9 +148,6 @@ def transform(
 | 
				
			||||||
        .all()
 | 
					        .all()
 | 
				
			||||||
    )
 | 
					    )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if len(players) == 0:
 | 
					 | 
				
			||||||
        return
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    if not existing_match:
 | 
					    if not existing_match:
 | 
				
			||||||
        match = Match()
 | 
					        match = Match()
 | 
				
			||||||
        match.logs_tf_id = log_id
 | 
					        match.logs_tf_id = log_id
 | 
				
			||||||
| 
						 | 
					@ -169,7 +160,8 @@ def transform(
 | 
				
			||||||
    else:
 | 
					    else:
 | 
				
			||||||
        match = existing_match
 | 
					        match = existing_match
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    #app_db.db.session.add(match)
 | 
					    if len(players) == 0:
 | 
				
			||||||
 | 
					        return
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    for player in players:
 | 
					    for player in players:
 | 
				
			||||||
        player_data = details["players"][steam64_to_steam3(player.steam_id)]
 | 
					        player_data = details["players"][steam64_to_steam3(player.steam_id)]
 | 
				
			||||||
| 
						 | 
					@ -230,18 +222,22 @@ def load_specific_match(id: int, team_id: int | None):
 | 
				
			||||||
    )
 | 
					    )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    raw_match = extract(id)
 | 
					    raw_match = extract(id)
 | 
				
			||||||
 | 
					    print("Loading match: " + str(id))
 | 
				
			||||||
    app_db.db.session.bulk_save_objects(transform(id, raw_match, match, team_id))
 | 
					    app_db.db.session.bulk_save_objects(transform(id, raw_match, match, team_id))
 | 
				
			||||||
    app_db.db.session.commit()
 | 
					    app_db.db.session.commit()
 | 
				
			||||||
    sleep(3) # avoid rate limiting if multiple tasks are queued
 | 
					    sleep(3)  # avoid rate limiting if multiple tasks are queued
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def main():
 | 
					@celery.task
 | 
				
			||||||
 | 
					def etl_periodic():
 | 
				
			||||||
    last: int = (
 | 
					    last: int = (
 | 
				
			||||||
        app_db.db.session.query(
 | 
					        app_db.db.session.query(
 | 
				
			||||||
            func.max(models.match.Match.logs_tf_id)
 | 
					            func.max(models.match.Match.logs_tf_id)
 | 
				
			||||||
        ).scalar()
 | 
					        ).scalar()
 | 
				
			||||||
    ) or 3767233
 | 
					    ) or 3768715
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    for summary in get_log_ids(last):
 | 
					    print("Last log ID: " + str(last))
 | 
				
			||||||
        print(summary)
 | 
					
 | 
				
			||||||
        sleep(3)
 | 
					    for id in get_log_ids(last):
 | 
				
			||||||
 | 
					        print("Found log: " + str(id))
 | 
				
			||||||
 | 
					        load_specific_match.delay(id, None)
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
		Reference in New Issue