def test_no_celery() -> None: with pytest.raises(ServiceUnavailable): connector.get_instance() log.warning("Skipping {} tests: service not available", CONNECTOR) return None
def test_no_celerybeat() -> None: obj = connector.get_instance() assert obj is not None with pytest.raises(AttributeError, match=r"Unsupported celery-beat scheduler: None"): # get_periodic_task with unknown CELERYBEAT_SCHEDULER obj.get_periodic_task("does_not_exist") with pytest.raises(AttributeError, match=r"Unsupported celery-beat scheduler: None"): # delete_periodic_task with unknown CELERYBEAT_SCHEDULER obj.delete_periodic_task("does_not_exist") with pytest.raises(AttributeError, match=r"Unsupported celery-beat scheduler: None"): # create_periodic_task with unknown CELERYBEAT_SCHEDULER obj.create_periodic_task(name="task1", task="task.does.not.exists", every="60") with pytest.raises(AttributeError, match=r"Unsupported celery-beat scheduler: None"): # create_crontab_task with unknown CELERYBEAT_SCHEDULER obj.create_crontab_task(name="task2", task="task.does.not.exists", minute="0", hour="1")
def send_task(app: Flask, task_name: str, *args: Any, **kwargs: Any) -> Any: c = celery.get_instance() c.app = app # Celery type hints are wrong!? # Mypy complains about: error: "Callable[[], Any]" has no attribute "get" # But .tasks is a TaskRegistry and it is child of dict... # so that .get is totally legit! task = c.celery_app.tasks.get(task_name) if not task: raise AttributeError("Task not found") with execute_from_code_dir(): return task(*args, **kwargs)
graph = neo4j.get_instance() datasets_to_analise = graph.Dataset.nodes.filter( status="UPLOAD COMPLETED").all() # get all the dataset uuid datasets_uuid = [x.uuid for x in datasets_to_analise] chunks_limit = Env.get_int("CHUNKS_LIMIT", 16) for chunk in [ datasets_uuid[i:i + chunks_limit] for i in range(0, len(datasets_uuid), chunks_limit) ]: log.info("Sending pipeline for datasets: {}", chunk) # pass the chunk to the celery task c = celery.get_instance() task = c.celery_app.send_task( "launch_pipeline", args=(chunk, ), countdown=1, ) log.info("{} datasets sent to task {}", len(chunk), task) # mark the related datasets as "QUEUED" for d in chunk: dataset = graph.Dataset.nodes.get_or_none(uuid=d) dataset.status = "QUEUED" dataset.save() log.info("Init pipeline cron completed\n")
def test_celery(app: Flask, faker: Faker) -> None: if not Connector.check_availability(CONNECTOR): try: obj = connector.get_instance() pytest.fail("No exception raised") # pragma: no cover except ServiceUnavailable: pass log.warning("Skipping {} tests: service not available", CONNECTOR) return None log.info("Executing {} tests", CONNECTOR) obj = connector.get_instance() assert obj is not None task = obj.celery_app.send_task("test_task") assert task is not None assert task.id is not None if obj.variables.get("backend") == "RABBIT": log.warning( "Due to limitations on RABBIT backend task results will not be tested" ) else: try: r = task.get(timeout=10) assert r is not None # This is the task output, as defined in task_template.py.j2 assert r == "Task executed!" assert task.status == "SUCCESS" assert task.result == "Task executed!" except celery.exceptions.TimeoutError: # pragma: no cover pytest.fail( f"Task timeout, result={task.result}, status={task.status}") if CeleryExt.CELERYBEAT_SCHEDULER is None: try: obj.get_periodic_task("does_not_exist") pytest.fail("get_periodic_task with unknown CELERYBEAT_SCHEDULER" ) # pragma: no cover except AttributeError as e: assert str(e) == "Unsupported celery-beat scheduler: None" except BaseException: # pragma: no cover pytest.fail("Unexpected exception raised") try: obj.delete_periodic_task("does_not_exist") pytest.fail( "delete_periodic_task with unknown CELERYBEAT_SCHEDULER" ) # pragma: no cover except AttributeError as e: assert str(e) == "Unsupported celery-beat scheduler: None" except BaseException: # pragma: no cover pytest.fail("Unexpected exception raised") try: obj.create_periodic_task(name="task1", task="task.does.not.exists", every="60") pytest.fail( "create_periodic_task with unknown CELERYBEAT_SCHEDULER" ) # pragma: no cover except AttributeError as e: assert str(e) == "Unsupported celery-beat scheduler: None" except BaseException: # pragma: no cover pytest.fail("Unexpected exception raised") try: obj.create_crontab_task(name="task2", task="task.does.not.exists", minute="0", hour="1") pytest.fail("create_crontab_task with unknown CELERYBEAT_SCHEDULER" ) # pragma: no cover except AttributeError as e: assert str(e) == "Unsupported celery-beat scheduler: None" except BaseException: # pragma: no cover pytest.fail("Unexpected exception raised") else: assert obj.get_periodic_task("does_not_exist") is None assert not obj.delete_periodic_task("does_not_exist") obj.create_periodic_task(name="task1", task="task.does.not.exists", every="60") assert obj.delete_periodic_task("task1") assert not obj.delete_periodic_task("task1") obj.create_periodic_task( name="task1_bis", task="task.does.not.exists", every="60", period="seconds", args=["a", "b", "c"], kwargs={ "a": 1, "b": 2, "c": 3 }, ) assert obj.delete_periodic_task("task1_bis") assert not obj.delete_periodic_task("task1_bis") # cron at 01:00 obj.create_crontab_task(name="task2", task="task.does.not.exists", minute="0", hour="1") assert obj.delete_periodic_task("task2") assert not obj.delete_periodic_task("task2") obj.create_crontab_task( name="task2_bis", task="task.does.not.exists", minute="0", hour="1", day_of_week="*", day_of_month="*", month_of_year="*", args=["a", "b", "c"], kwargs={ "a": 1, "b": 2, "c": 3 }, ) assert obj.delete_periodic_task("task2_bis") assert not obj.delete_periodic_task("task2_bis") if CeleryExt.CELERYBEAT_SCHEDULER == "REDIS": obj.create_periodic_task( name="task3", task="task.does.not.exists", every=60, ) assert obj.delete_periodic_task("task3") obj.create_periodic_task(name="task4", task="task.does.not.exists", every=60, period="seconds") assert obj.delete_periodic_task("task4") obj.create_periodic_task(name="task5", task="task.does.not.exists", every=60, period="minutes") assert obj.delete_periodic_task("task5") obj.create_periodic_task(name="task6", task="task.does.not.exists", every=60, period="hours") assert obj.delete_periodic_task("task6") obj.create_periodic_task(name="task7", task="task.does.not.exists", every=60, period="days") assert obj.delete_periodic_task("task7") try: obj.create_periodic_task( name="task8", task="task.does.not.exists", every="60", period="years", # type: ignore ) except BadRequest as e: assert str(e) == "Invalid timedelta period: years" obj.create_periodic_task( name="task9", task="task.does.not.exists", every=timedelta(seconds=60), ) assert obj.delete_periodic_task("task9") try: obj.create_periodic_task( name="task10", task="task.does.not.exists", every=["60"], # type: ignore ) except AttributeError as e: assert str( e) == "Invalid input parameter every = ['60'] (type list)" try: obj.create_periodic_task( name="task11", task="task.does.not.exists", every="invalid", ) except AttributeError as e: assert str( e) == "Invalid input parameter every = invalid (type str)" else: obj.create_periodic_task(name="task3", task="task.does.not.exists", every="60", period="minutes") assert obj.delete_periodic_task("task3") obj.disconnect() # a second disconnect should not raise any error obj.disconnect() # Create new connector with short expiration time obj = connector.get_instance(expiration=2, verification=1) obj_id = id(obj) # Connector is expected to be still valid obj = connector.get_instance(expiration=2, verification=1) assert id(obj) == obj_id time.sleep(1) # The connection should have been checked and should be still valid obj = connector.get_instance(expiration=2, verification=1) assert id(obj) == obj_id time.sleep(1) # Connection should have been expired and a new connector been created obj = connector.get_instance(expiration=2, verification=1) assert id(obj) != obj_id assert obj.is_connected() obj.disconnect() assert not obj.is_connected() # ... close connection again ... nothing should happens obj.disconnect() with connector.get_instance() as obj: assert obj is not None app = create_app(mode=ServerModes.WORKER) assert app is not None from restapi.utilities.logs import LOGS_FILE assert os.environ["HOSTNAME"] == "backend-server" assert LOGS_FILE == "backend-server" # this decorator is expected to be used in celery context, i.e. the self reference # should contains a request, injected by celery. Let's mock this by injecting an # artificial self @send_errors_by_email def this_function_raises_exceptions(self): raise AttributeError("Just an exception") class FakeRequest: def __init__(self, task_id, task, args): self.id = task_id self.task = task self.args = args class FakeSelf: def __init__(self, task_id, task, args): self.request = FakeRequest(task_id, task, args) task_id = faker.pystr() task_name = faker.pystr() task_args = [faker.pystr()] this_function_raises_exceptions(FakeSelf(task_id, task_name, task_args)) mail = BaseTests.read_mock_email() assert mail.get("body") is not None assert f"Celery task {task_id} failed" in mail.get("body") assert f"Name: {task_name}" in mail.get("body") assert f"Arguments: {str(task_args)}" in mail.get("body") assert "Error: Traceback (most recent call last):" in mail.get("body") assert 'raise AttributeError("Just an exception")' in mail.get("body")
def test_celery(app: Flask, faker: Faker) -> None: log.info("Executing {} tests", CONNECTOR) obj = connector.get_instance() assert obj is not None task = obj.celery_app.send_task("test_task", args=("myinput", )) assert task is not None assert task.id is not None # Mocked task task_output = BaseTests.send_task(app, "test_task", "myinput") # As defined in task template assert task_output == "Task executed!" # wrong is a special value included in tasks template with pytest.raises(Ignore): BaseTests.send_task(app, "test_task", "wrong") project_title = get_project_configuration("project.title", default="YourProject") mail = BaseTests.read_mock_email() body = mail.get("body") headers = mail.get("headers") assert body is not None assert headers is not None assert f"Subject: {project_title}: Task test_task failed" in headers assert "this email is to notify you that a Celery task failed!" in body # fixed-id is a mocked value set in TESTING mode by @task in Celery connector assert "Task ID: fixed-id" in body assert "Task name: test_task" in body assert "Arguments: ('wrong',)" in body assert "Error Stack" in body assert "Traceback (most recent call last):" in body exc = ( "AttributeError: " "You can raise exceptions to stop the task execution in case of errors" ) assert exc in body # celery.exceptions.Ignore exceptions are ignored BaseTests.delete_mock_email() # ignore is a special value included in tasks template with pytest.raises(Ignore): BaseTests.send_task(app, "test_task", "ignore") # the errors decorator re-raise the Ignore exception, without any further action # No email is sent in case of Ignore exceptions with pytest.raises(FileNotFoundError): mail = BaseTests.read_mock_email() # retry is a special value included in tasks template with pytest.raises(CeleryRetryTask): BaseTests.send_task(app, "test_task", "retry") mail = BaseTests.read_mock_email() body = mail.get("body") headers = mail.get("headers") assert body is not None assert headers is not None assert f"Subject: {project_title}: Task test_task failed (failure #1)" in headers assert "this email is to notify you that a Celery task failed!" in body # fixed-id is a mocked value set in TESTING mode by @task in Celery connector assert "Task ID: fixed-id" in body assert "Task name: test_task" in body assert "Arguments: ('retry',)" in body assert "Error Stack" in body assert "Traceback (most recent call last):" in body exc = "CeleryRetryTask: Force the retry of this task" assert exc in body # retry2 is a special value included in tasks template # Can't easily import the custom exception defined in the task... # a generic exception is enough here with pytest.raises(Exception): BaseTests.send_task(app, "test_task", "retry2") mail = BaseTests.read_mock_email() body = mail.get("body") headers = mail.get("headers") assert body is not None assert headers is not None assert f"Subject: {project_title}: Task test_task failed (failure #1)" in headers assert "this email is to notify you that a Celery task failed!" in body # fixed-id is a mocked value set in TESTING mode by @task in Celery connector assert "Task ID: fixed-id" in body assert "Task name: test_task" in body assert "Arguments: ('retry2',)" in body assert "Error Stack" in body assert "Traceback (most recent call last):" in body exc = "MyException: Force the retry of this task by using a custom exception" assert exc in body with pytest.raises(AttributeError, match=r"Task not found"): BaseTests.send_task(app, "does-not-exist") if obj.variables.get("backend_service") == "RABBIT": log.warning( "Due to limitations on RABBIT backend task results will not be tested" ) else: try: r = task.get(timeout=10) assert r is not None # This is the task output, as defined in task_template.py.j2 assert r == "Task executed!" assert task.status == "SUCCESS" assert task.result == "Task executed!" except celery.exceptions.TimeoutError: # pragma: no cover pytest.fail( f"Task timeout, result={task.result}, status={task.status}") obj.disconnect() # a second disconnect should not raise any error obj.disconnect() # Create new connector with short expiration time obj = connector.get_instance(expiration=2, verification=1) obj_id = id(obj) # Connector is expected to be still valid obj = connector.get_instance(expiration=2, verification=1) assert id(obj) == obj_id time.sleep(1) # The connection should have been checked and should be still valid obj = connector.get_instance(expiration=2, verification=1) assert id(obj) == obj_id time.sleep(1) # Connection should have been expired and a new connector been created obj = connector.get_instance(expiration=2, verification=1) assert id(obj) != obj_id assert obj.is_connected() obj.disconnect() assert not obj.is_connected() # ... close connection again ... nothing should happen obj.disconnect() with connector.get_instance() as obj: assert obj is not None app = create_app(mode=ServerModes.WORKER) assert app is not None
def test_celerybeat() -> None: obj = connector.get_instance() assert obj is not None assert obj.get_periodic_task("does_not_exist") is None assert not obj.delete_periodic_task("does_not_exist") obj.create_periodic_task(name="task1", task="task.does.not.exists", every="60") assert obj.delete_periodic_task("task1") assert not obj.delete_periodic_task("task1") obj.create_periodic_task( name="task1_bis", task="task.does.not.exists", every="60", period="seconds", args=["a", "b", "c"], kwargs={ "a": 1, "b": 2, "c": 3 }, ) assert obj.delete_periodic_task("task1_bis") assert not obj.delete_periodic_task("task1_bis") # cron at 01:00 obj.create_crontab_task(name="task2", task="task.does.not.exists", minute="0", hour="1") assert obj.delete_periodic_task("task2") assert not obj.delete_periodic_task("task2") obj.create_crontab_task( name="task2_bis", task="task.does.not.exists", minute="0", hour="1", day_of_week="*", day_of_month="*", month_of_year="*", args=["a", "b", "c"], kwargs={ "a": 1, "b": 2, "c": 3 }, ) assert obj.delete_periodic_task("task2_bis") assert not obj.delete_periodic_task("task2_bis") if CeleryExt.CELERYBEAT_SCHEDULER == "REDIS": obj.create_periodic_task( name="task3", task="task.does.not.exists", every=60, ) assert obj.delete_periodic_task("task3") obj.create_periodic_task(name="task4", task="task.does.not.exists", every=60, period="seconds") assert obj.delete_periodic_task("task4") obj.create_periodic_task(name="task5", task="task.does.not.exists", every=60, period="minutes") assert obj.delete_periodic_task("task5") obj.create_periodic_task(name="task6", task="task.does.not.exists", every=60, period="hours") assert obj.delete_periodic_task("task6") obj.create_periodic_task(name="task7", task="task.does.not.exists", every=60, period="days") assert obj.delete_periodic_task("task7") with pytest.raises(BadRequest, match=r"Invalid timedelta period: years"): obj.create_periodic_task( name="task8", task="task.does.not.exists", every="60", period="years", # type: ignore ) obj.create_periodic_task( name="task9", task="task.does.not.exists", every=timedelta(seconds=60), ) assert obj.delete_periodic_task("task9") with pytest.raises( AttributeError, match=r"Invalid input parameter every = \['60'\] \(type list\)", ): obj.create_periodic_task( name="task10", task="task.does.not.exists", every=["60"], # type: ignore ) with pytest.raises( AttributeError, match=r"Invalid input parameter every = invalid \(type str\)", ): obj.create_periodic_task( name="task11", task="task.does.not.exists", every="invalid", )