Repository: getsentry/sentry-python

Issue:
Sub minute intervals for Celery Cron integrations
We do NOT support sub minute intervals in Sentry Crons. There when where would be "interval" type schedule that is sub minute, we should print a warning and return an empty monitor config:

See code here:
[[_get_monitor_confi](https://github.com/getsentry/sentry-python/blob/a1c27600625d20803fc58a008172dcc11d053a01/sentry_sdk/integrations/celery.py#L396-L407)](https://github.com/getsentry/sentry-python/blob/a1c27600625d20803fc58a008172dcc11d053a01/sentry_sdk/integrations/celery.py#L396-L407)

In the `elif` there should be warning and a return {} like in the `else` below in case the interval is under a minute.


Discussion:
Yeah specifically change this return line here:
https://github.com/getsentry/sentry-python/blob/a1c27600625d20803fc58a008172dcc11d053a01/sentry_sdk/integrations/celery.py#L377

Resolving patch:
diff --git a/sentry_sdk/integrations/celery.py b/sentry_sdk/integrations/celery.py
--- a/sentry_sdk/integrations/celery.py
+++ b/sentry_sdk/integrations/celery.py
@@ -375,7 +375,7 @@ def _get_humanized_interval(seconds):
             interval = int(seconds / divider)
             return (interval, unit)
 
-    return (1, "minute")
+    return (int(seconds), "second")
 
 
 def _get_monitor_config(celery_schedule, app):
@@ -400,6 +400,12 @@ def _get_monitor_config(celery_schedule, app):
             celery_schedule.seconds
         )
 
+        if schedule_unit == "second":
+            logger.warning(
+                "Intervals shorter than one minute are not supported by Sentry Crons."
+            )
+            return {}
+
     else:
         logger.warning(
             "Celery schedule type '%s' not supported by Sentry Crons.",
@@ -441,24 +447,27 @@ def sentry_apply_entry(*args, **kwargs):
 
         monitor_config = _get_monitor_config(celery_schedule, app)
 
-        headers = schedule_entry.options.pop("headers", {})
-        headers.update(
-            {
-                "sentry-monitor-slug": monitor_name,
-                "sentry-monitor-config": monitor_config,
-            }
-        )
-
-        check_in_id = capture_checkin(
-            monitor_slug=monitor_name,
-            monitor_config=monitor_config,
-            status=MonitorStatus.IN_PROGRESS,
-        )
-        headers.update({"sentry-monitor-check-in-id": check_in_id})
+        is_supported_schedule = bool(monitor_config)
+        if is_supported_schedule:
+            headers = schedule_entry.options.pop("headers", {})
+            headers.update(
+                {
+                    "sentry-monitor-slug": monitor_name,
+                    "sentry-monitor-config": monitor_config,
+                }
+            )
+
+            check_in_id = capture_checkin(
+                monitor_slug=monitor_name,
+                monitor_config=monitor_config,
+                status=MonitorStatus.IN_PROGRESS,
+            )
+            headers.update({"sentry-monitor-check-in-id": check_in_id})
+
+            # Set the Sentry configuration in the options of the ScheduleEntry.
+            # Those will be picked up in `apply_async` and added to the headers.
+            schedule_entry.options["headers"] = headers
 
-        # Set the Sentry configuration in the options of the ScheduleEntry.
-        # Those will be picked up in `apply_async` and added to the headers.
-        schedule_entry.options["headers"] = headers
         return original_apply_entry(*args, **kwargs)
 
     Scheduler.apply_entry = sentry_apply_entry


Test patch:
diff --git a/tests/integrations/celery/test_celery_beat_crons.py b/tests/integrations/celery/test_celery_beat_crons.py
--- a/tests/integrations/celery/test_celery_beat_crons.py
+++ b/tests/integrations/celery/test_celery_beat_crons.py
@@ -59,9 +59,11 @@ def test_get_headers():
 @pytest.mark.parametrize(
     "seconds, expected_tuple",
     [
-        (0, (1, "minute")),
-        (0.00001, (1, "minute")),
-        (1, (1, "minute")),
+        (0, (0, "second")),
+        (1, (1, "second")),
+        (0.00001, (0, "second")),
+        (59, (59, "second")),
+        (60, (1, "minute")),
         (100, (1, "minute")),
         (1000, (16, "minute")),
         (10000, (2, "hour")),
@@ -205,13 +207,12 @@ def test_crons_task_retry():
             )
 
 
-def test_get_monitor_config():
+def test_get_monitor_config_crontab():
     app = MagicMock()
     app.conf = MagicMock()
     app.conf.timezone = "Europe/Vienna"
 
     celery_schedule = crontab(day_of_month="3", hour="12", minute="*/10")
-
     monitor_config = _get_monitor_config(celery_schedule, app)
     assert monitor_config == {
         "schedule": {
@@ -222,8 +223,23 @@ def test_get_monitor_config():
     }
     assert "unit" not in monitor_config["schedule"]
 
-    celery_schedule = schedule(run_every=3)
 
+def test_get_monitor_config_seconds():
+    app = MagicMock()
+    app.conf = MagicMock()
+    app.conf.timezone = "Europe/Vienna"
+
+    celery_schedule = schedule(run_every=3)  # seconds
+    monitor_config = _get_monitor_config(celery_schedule, app)
+    assert monitor_config == {}
+
+
+def test_get_monitor_config_minutes():
+    app = MagicMock()
+    app.conf = MagicMock()
+    app.conf.timezone = "Europe/Vienna"
+
+    celery_schedule = schedule(run_every=60)  # seconds
     monitor_config = _get_monitor_config(celery_schedule, app)
     assert monitor_config == {
         "schedule": {
@@ -234,6 +250,12 @@ def test_get_monitor_config():
         "timezone": "Europe/Vienna",
     }
 
+
+def test_get_monitor_config_unknown():
+    app = MagicMock()
+    app.conf = MagicMock()
+    app.conf.timezone = "Europe/Vienna"
+
     unknown_celery_schedule = MagicMock()
     monitor_config = _get_monitor_config(unknown_celery_schedule, app)
     assert monitor_config == {}