We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
Hardcoded MAX_RETRIES = 1
https://github.com/getsentry/sentry/blob/master/src/sentry/tasks/deletion.py#L18

--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `src/sentry/tasks/deletion.py`
Content:
```
1 from __future__ import absolute_import
2 
3 from uuid import uuid4
4 
5 from django.apps import apps
6 from django.conf import settings
7 from django.db import transaction
8 from django.utils import timezone
9 
10 from sentry.constants import ObjectStatus
11 from sentry.exceptions import DeleteAborted
12 from sentry.signals import pending_delete
13 from sentry.tasks.base import instrumented_task, retry, track_group_async_operation
14 
15 # in prod we run with infinite retries to recover from errors
16 # in debug/development, we assume these tasks generally shouldn't fail
17 MAX_RETRIES = 1 if settings.DEBUG else None
18 MAX_RETRIES = 1
19 
20 
21 @instrumented_task(name="sentry.tasks.deletion.run_scheduled_deletions", queue="cleanup")
22 def run_scheduled_deletions():
23     from sentry.models import ScheduledDeletion
24 
25     queryset = ScheduledDeletion.objects.filter(
26         in_progress=False, aborted=False, date_scheduled__lte=timezone.now()
27     )
28     for item in queryset:
29         with transaction.atomic():
30             affected = ScheduledDeletion.objects.filter(
31                 id=item.id, in_progress=False, aborted=False
32             ).update(in_progress=True)
33             if not affected:
34                 continue
35 
36             run_deletion.delay(deletion_id=item.id)
37 
38 
39 @instrumented_task(
40     name="sentry.tasks.deletion.run_deletion",
41     queue="cleanup",
42     default_retry_delay=60 * 5,
43     max_retries=MAX_RETRIES,
44 )
45 @retry(exclude=(DeleteAborted,))
46 def run_deletion(deletion_id):
47     from sentry import deletions
48     from sentry.models import ScheduledDeletion
49 
50     try:
51         deletion = ScheduledDeletion.objects.get(id=deletion_id)
52     except ScheduledDeletion.DoesNotExist:
53         return
54 
55     if deletion.aborted:
56         raise DeleteAborted
57 
58     if not deletion.in_progress:
59         actor = deletion.get_actor()
60         instance = deletion.get_instance()
61         with transaction.atomic():
62             deletion.update(in_progress=True)
63             pending_delete.send(sender=type(instance), instance=instance, actor=actor)
64 
65     task = deletions.get(
66         model=deletion.get_model(),
67         query={"id": deletion.object_id},
68         transaction_id=deletion.guid,
69         actor_id=deletion.actor_id,
70     )
71     has_more = task.chunk()
72     if has_more:
73         run_deletion.apply_async(kwargs={"deletion_id": deletion_id}, countdown=15)
74     deletion.delete()
75 
76 
77 @instrumented_task(
78     name="sentry.tasks.deletion.revoke_api_tokens",
79     queue="cleanup",
80     default_retry_delay=60 * 5,
81     max_retries=MAX_RETRIES,
82 )
83 @retry(exclude=(DeleteAborted,))
84 def revoke_api_tokens(object_id, transaction_id=None, timestamp=None, **kwargs):
85     from sentry.models import ApiToken
86 
87     queryset = ApiToken.objects.filter(application=object_id)
88     if timestamp:
89         queryset = queryset.filter(date_added__lte=timestamp)
90 
91     # we're using a slow deletion strategy to avoid a lot of custom code for
92     # postgres
93     has_more = False
94     for obj in queryset[:1000]:
95         obj.delete()
96         has_more = True
97 
98     if has_more:
99         revoke_api_tokens.apply_async(
100             kwargs={
101                 "object_id": object_id,
102                 "transaction_id": transaction_id,
103                 "timestamp": timestamp,
104             },
105             countdown=15,
106         )
107     return has_more
108 
109 
110 @instrumented_task(
111     name="sentry.tasks.deletion.delete_organization",
112     queue="cleanup",
113     default_retry_delay=60 * 5,
114     max_retries=MAX_RETRIES,
115 )
116 @retry(exclude=(DeleteAborted,))
117 def delete_organization(object_id, transaction_id=None, actor_id=None, **kwargs):
118     from sentry import deletions
119     from sentry.models import Organization, OrganizationStatus
120 
121     try:
122         instance = Organization.objects.get(id=object_id)
123     except Organization.DoesNotExist:
124         return
125 
126     if instance.status == OrganizationStatus.VISIBLE:
127         raise DeleteAborted
128 
129     # compat: can be removed after we switch to scheduled deletions
130     if instance.status != OrganizationStatus.DELETION_IN_PROGRESS:
131         pending_delete.send(sender=type(instance), instance=instance)
132 
133     task = deletions.get(
134         model=Organization,
135         query={"id": object_id},
136         transaction_id=transaction_id or uuid4().hex,
137         actor_id=actor_id,
138     )
139     has_more = task.chunk()
140     if has_more:
141         delete_organization.apply_async(
142             kwargs={"object_id": object_id, "transaction_id": transaction_id, "actor_id": actor_id},
143             countdown=15,
144         )
145 
146 
147 @instrumented_task(
148     name="sentry.tasks.deletion.delete_team",
149     queue="cleanup",
150     default_retry_delay=60 * 5,
151     max_retries=MAX_RETRIES,
152 )
153 @retry(exclude=(DeleteAborted,))
154 def delete_team(object_id, transaction_id=None, **kwargs):
155     from sentry import deletions
156     from sentry.models import Team, TeamStatus
157 
158     try:
159         instance = Team.objects.get(id=object_id)
160     except Team.DoesNotExist:
161         return
162 
163     if instance.status == TeamStatus.VISIBLE:
164         raise DeleteAborted
165 
166     task = deletions.get(
167         model=Team, query={"id": object_id}, transaction_id=transaction_id or uuid4().hex
168     )
169     has_more = task.chunk()
170     if has_more:
171         delete_team.apply_async(
172             kwargs={"object_id": object_id, "transaction_id": transaction_id}, countdown=15
173         )
174 
175 
176 @instrumented_task(
177     name="sentry.tasks.deletion.delete_project",
178     queue="cleanup",
179     default_retry_delay=60 * 5,
180     max_retries=MAX_RETRIES,
181 )
182 @retry(exclude=(DeleteAborted,))
183 def delete_project(object_id, transaction_id=None, **kwargs):
184     from sentry import deletions
185     from sentry.models import Project, ProjectStatus
186 
187     try:
188         instance = Project.objects.get(id=object_id)
189     except Project.DoesNotExist:
190         return
191 
192     if instance.status == ProjectStatus.VISIBLE:
193         raise DeleteAborted
194 
195     task = deletions.get(
196         model=Project, query={"id": object_id}, transaction_id=transaction_id or uuid4().hex
197     )
198     has_more = task.chunk()
199     if has_more:
200         delete_project.apply_async(
201             kwargs={"object_id": object_id, "transaction_id": transaction_id}, countdown=15
202         )
203 
204 
205 @instrumented_task(
206     name="sentry.tasks.deletion.delete_groups",
207     queue="cleanup",
208     default_retry_delay=60 * 5,
209     max_retries=MAX_RETRIES,
210 )
211 @retry(exclude=(DeleteAborted,))
212 @track_group_async_operation
213 def delete_groups(object_ids, transaction_id=None, eventstream_state=None, **kwargs):
214     from sentry import deletions, eventstream
215     from sentry.models import Group
216 
217     transaction_id = transaction_id or uuid4().hex
218 
219     max_batch_size = 100
220     current_batch, rest = object_ids[:max_batch_size], object_ids[max_batch_size:]
221 
222     task = deletions.get(
223         model=Group, query={"id__in": current_batch}, transaction_id=transaction_id
224     )
225     has_more = task.chunk()
226     if has_more or rest:
227         delete_groups.apply_async(
228             kwargs={
229                 "object_ids": object_ids if has_more else rest,
230                 "transaction_id": transaction_id,
231                 "eventstream_state": eventstream_state,
232             },
233             countdown=15,
234         )
235     else:
236         # all groups have been deleted
237         if eventstream_state:
238             eventstream.end_delete_groups(eventstream_state)
239 
240 
241 @instrumented_task(
242     name="sentry.tasks.deletion.delete_api_application",
243     queue="cleanup",
244     default_retry_delay=60 * 5,
245     max_retries=MAX_RETRIES,
246 )
247 @retry(exclude=(DeleteAborted,))
248 def delete_api_application(object_id, transaction_id=None, **kwargs):
249     from sentry import deletions
250     from sentry.models import ApiApplication, ApiApplicationStatus
251 
252     try:
253         instance = ApiApplication.objects.get(id=object_id)
254     except ApiApplication.DoesNotExist:
255         return
256 
257     if instance.status == ApiApplicationStatus.active:
258         raise DeleteAborted
259 
260     task = deletions.get(
261         model=ApiApplication, query={"id": object_id}, transaction_id=transaction_id or uuid4().hex
262     )
263     has_more = task.chunk()
264     if has_more:
265         delete_api_application.apply_async(
266             kwargs={"object_id": object_id, "transaction_id": transaction_id}, countdown=15
267         )
268 
269 
270 @instrumented_task(
271     name="sentry.tasks.deletion.generic_delete",
272     queue="cleanup",
273     default_retry_delay=60 * 5,
274     max_retries=MAX_RETRIES,
275 )
276 @retry(exclude=(DeleteAborted,))
277 def generic_delete(app_label, model_name, object_id, transaction_id=None, actor_id=None, **kwargs):
278     from sentry import deletions
279     from sentry.models import User
280 
281     model = apps.get_model(app_label, model_name)
282 
283     try:
284         instance = model.objects.get(id=object_id)
285     except model.DoesNotExist:
286         return
287 
288     if instance.status != ObjectStatus.DELETION_IN_PROGRESS:
289         pending_delete.send(
290             sender=type(instance),
291             instance=instance,
292             actor=User.objects.get(id=actor_id) if actor_id else None,
293         )
294 
295     if instance.status == ObjectStatus.VISIBLE:
296         raise DeleteAborted
297 
298     task = deletions.get(
299         model=model,
300         actor_id=actor_id,
301         query={"id": object_id},
302         transaction_id=transaction_id or uuid4().hex,
303     )
304     has_more = task.chunk()
305     if has_more:
306         generic_delete.apply_async(
307             kwargs={
308                 "app_label": app_label,
309                 "model_name": model_name,
310                 "object_id": object_id,
311                 "transaction_id": transaction_id,
312                 "actor_id": actor_id,
313             },
314             countdown=15,
315         )
316 
317 
318 @instrumented_task(
319     name="sentry.tasks.deletion.delete_repository",
320     queue="cleanup",
321     default_retry_delay=60 * 5,
322     max_retries=MAX_RETRIES,
323 )
324 @retry(exclude=(DeleteAborted,))
325 def delete_repository(object_id, transaction_id=None, actor_id=None, **kwargs):
326     from sentry import deletions
327     from sentry.models import Repository, User
328 
329     try:
330         instance = Repository.objects.get(id=object_id)
331     except Repository.DoesNotExist:
332         return
333 
334     if instance.status == ObjectStatus.VISIBLE:
335         raise DeleteAborted
336 
337     # compat: can be removed after we switch to scheduled deletions
338     if instance.status != ObjectStatus.DELETION_IN_PROGRESS:
339         pending_delete.send(
340             sender=type(instance),
341             instance=instance,
342             actor=User.objects.get(id=actor_id) if actor_id else None,
343         )
344 
345     task = deletions.get(
346         model=Repository,
347         actor_id=actor_id,
348         query={"id": object_id},
349         transaction_id=transaction_id or uuid4().hex,
350     )
351     has_more = task.chunk()
352     if has_more:
353         delete_repository.apply_async(
354             kwargs={"object_id": object_id, "transaction_id": transaction_id, "actor_id": actor_id},
355             countdown=15,
356         )
357 
358 
359 @instrumented_task(
360     name="sentry.tasks.deletion.delete_organization_integration",
361     queue="cleanup",
362     default_retry_delay=60 * 5,
363     max_retries=MAX_RETRIES,
364 )
365 @retry(exclude=(DeleteAborted,))
366 def delete_organization_integration(object_id, transaction_id=None, actor_id=None, **kwargs):
367     from sentry import deletions
368     from sentry.models import OrganizationIntegration, Repository
369 
370     try:
371         instance = OrganizationIntegration.objects.get(id=object_id)
372     except OrganizationIntegration.DoesNotExist:
373         return
374 
375     if instance.status == ObjectStatus.VISIBLE:
376         raise DeleteAborted
377 
378     # dissociate repos from that integration
379     Repository.objects.filter(
380         organization_id=instance.organization_id, integration_id=instance.integration_id
381     ).update(integration_id=None)
382 
383     task = deletions.get(
384         model=OrganizationIntegration,
385         actor_id=actor_id,
386         query={"id": object_id},
387         transaction_id=transaction_id or uuid4().hex,
388     )
389     has_more = task.chunk()
390     if has_more:
391         delete_organization_integration.apply_async(
392             kwargs={"object_id": object_id, "transaction_id": transaction_id, "actor_id": actor_id},
393             countdown=15,
394         )
395
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/src/sentry/tasks/deletion.py b/src/sentry/tasks/deletion.py
--- a/src/sentry/tasks/deletion.py
+++ b/src/sentry/tasks/deletion.py
@@ -14,8 +14,7 @@
 
 # in prod we run with infinite retries to recover from errors
 # in debug/development, we assume these tasks generally shouldn't fail
-MAX_RETRIES = 1 if settings.DEBUG else None
-MAX_RETRIES = 1
+MAX_RETRIES = 1 if settings.DEBUG else 5
 
 
 @instrumented_task(name="sentry.tasks.deletion.run_scheduled_deletions", queue="cleanup")

{"golden_diff": "diff --git a/src/sentry/tasks/deletion.py b/src/sentry/tasks/deletion.py\n--- a/src/sentry/tasks/deletion.py\n+++ b/src/sentry/tasks/deletion.py\n@@ -14,8 +14,7 @@\n \n # in prod we run with infinite retries to recover from errors\n # in debug/development, we assume these tasks generally shouldn't fail\n-MAX_RETRIES = 1 if settings.DEBUG else None\n-MAX_RETRIES = 1\n+MAX_RETRIES = 1 if settings.DEBUG else 5\n \n \n @instrumented_task(name=\"sentry.tasks.deletion.run_scheduled_deletions\", queue=\"cleanup\")\n", "issue": "Hardcoded MAX_RETRIES = 1\nhttps://github.com/getsentry/sentry/blob/master/src/sentry/tasks/deletion.py#L18\n", "before_files": [{"content": "from __future__ import absolute_import\n\nfrom uuid import uuid4\n\nfrom django.apps import apps\nfrom django.conf import settings\nfrom django.db import transaction\nfrom django.utils import timezone\n\nfrom sentry.constants import ObjectStatus\nfrom sentry.exceptions import DeleteAborted\nfrom sentry.signals import pending_delete\nfrom sentry.tasks.base import instrumented_task, retry, track_group_async_operation\n\n# in prod we run with infinite retries to recover from errors\n# in debug/development, we assume these tasks generally shouldn't fail\nMAX_RETRIES = 1 if settings.DEBUG else None\nMAX_RETRIES = 1\n\n\n@instrumented_task(name=\"sentry.tasks.deletion.run_scheduled_deletions\", queue=\"cleanup\")\ndef run_scheduled_deletions():\n    from sentry.models import ScheduledDeletion\n\n    queryset = ScheduledDeletion.objects.filter(\n        in_progress=False, aborted=False, date_scheduled__lte=timezone.now()\n    )\n    for item in queryset:\n        with transaction.atomic():\n            affected = ScheduledDeletion.objects.filter(\n                id=item.id, in_progress=False, aborted=False\n            ).update(in_progress=True)\n            if not affected:\n                continue\n\n            run_deletion.delay(deletion_id=item.id)\n\n\n@instrumented_task(\n    name=\"sentry.tasks.deletion.run_deletion\",\n    queue=\"cleanup\",\n    default_retry_delay=60 * 5,\n    max_retries=MAX_RETRIES,\n)\n@retry(exclude=(DeleteAborted,))\ndef run_deletion(deletion_id):\n    from sentry import deletions\n    from sentry.models import ScheduledDeletion\n\n    try:\n        deletion = ScheduledDeletion.objects.get(id=deletion_id)\n    except ScheduledDeletion.DoesNotExist:\n        return\n\n    if deletion.aborted:\n        raise DeleteAborted\n\n    if not deletion.in_progress:\n        actor = deletion.get_actor()\n        instance = deletion.get_instance()\n        with transaction.atomic():\n            deletion.update(in_progress=True)\n            pending_delete.send(sender=type(instance), instance=instance, actor=actor)\n\n    task = deletions.get(\n        model=deletion.get_model(),\n        query={\"id\": deletion.object_id},\n        transaction_id=deletion.guid,\n        actor_id=deletion.actor_id,\n    )\n    has_more = task.chunk()\n    if has_more:\n        run_deletion.apply_async(kwargs={\"deletion_id\": deletion_id}, countdown=15)\n    deletion.delete()\n\n\n@instrumented_task(\n    name=\"sentry.tasks.deletion.revoke_api_tokens\",\n    queue=\"cleanup\",\n    default_retry_delay=60 * 5,\n    max_retries=MAX_RETRIES,\n)\n@retry(exclude=(DeleteAborted,))\ndef revoke_api_tokens(object_id, transaction_id=None, timestamp=None, **kwargs):\n    from sentry.models import ApiToken\n\n    queryset = ApiToken.objects.filter(application=object_id)\n    if timestamp:\n        queryset = queryset.filter(date_added__lte=timestamp)\n\n    # we're using a slow deletion strategy to avoid a lot of custom code for\n    # postgres\n    has_more = False\n    for obj in queryset[:1000]:\n        obj.delete()\n        has_more = True\n\n    if has_more:\n        revoke_api_tokens.apply_async(\n            kwargs={\n                \"object_id\": object_id,\n                \"transaction_id\": transaction_id,\n                \"timestamp\": timestamp,\n            },\n            countdown=15,\n        )\n    return has_more\n\n\n@instrumented_task(\n    name=\"sentry.tasks.deletion.delete_organization\",\n    queue=\"cleanup\",\n    default_retry_delay=60 * 5,\n    max_retries=MAX_RETRIES,\n)\n@retry(exclude=(DeleteAborted,))\ndef delete_organization(object_id, transaction_id=None, actor_id=None, **kwargs):\n    from sentry import deletions\n    from sentry.models import Organization, OrganizationStatus\n\n    try:\n        instance = Organization.objects.get(id=object_id)\n    except Organization.DoesNotExist:\n        return\n\n    if instance.status == OrganizationStatus.VISIBLE:\n        raise DeleteAborted\n\n    # compat: can be removed after we switch to scheduled deletions\n    if instance.status != OrganizationStatus.DELETION_IN_PROGRESS:\n        pending_delete.send(sender=type(instance), instance=instance)\n\n    task = deletions.get(\n        model=Organization,\n        query={\"id\": object_id},\n        transaction_id=transaction_id or uuid4().hex,\n        actor_id=actor_id,\n    )\n    has_more = task.chunk()\n    if has_more:\n        delete_organization.apply_async(\n            kwargs={\"object_id\": object_id, \"transaction_id\": transaction_id, \"actor_id\": actor_id},\n            countdown=15,\n        )\n\n\n@instrumented_task(\n    name=\"sentry.tasks.deletion.delete_team\",\n    queue=\"cleanup\",\n    default_retry_delay=60 * 5,\n    max_retries=MAX_RETRIES,\n)\n@retry(exclude=(DeleteAborted,))\ndef delete_team(object_id, transaction_id=None, **kwargs):\n    from sentry import deletions\n    from sentry.models import Team, TeamStatus\n\n    try:\n        instance = Team.objects.get(id=object_id)\n    except Team.DoesNotExist:\n        return\n\n    if instance.status == TeamStatus.VISIBLE:\n        raise DeleteAborted\n\n    task = deletions.get(\n        model=Team, query={\"id\": object_id}, transaction_id=transaction_id or uuid4().hex\n    )\n    has_more = task.chunk()\n    if has_more:\n        delete_team.apply_async(\n            kwargs={\"object_id\": object_id, \"transaction_id\": transaction_id}, countdown=15\n        )\n\n\n@instrumented_task(\n    name=\"sentry.tasks.deletion.delete_project\",\n    queue=\"cleanup\",\n    default_retry_delay=60 * 5,\n    max_retries=MAX_RETRIES,\n)\n@retry(exclude=(DeleteAborted,))\ndef delete_project(object_id, transaction_id=None, **kwargs):\n    from sentry import deletions\n    from sentry.models import Project, ProjectStatus\n\n    try:\n        instance = Project.objects.get(id=object_id)\n    except Project.DoesNotExist:\n        return\n\n    if instance.status == ProjectStatus.VISIBLE:\n        raise DeleteAborted\n\n    task = deletions.get(\n        model=Project, query={\"id\": object_id}, transaction_id=transaction_id or uuid4().hex\n    )\n    has_more = task.chunk()\n    if has_more:\n        delete_project.apply_async(\n            kwargs={\"object_id\": object_id, \"transaction_id\": transaction_id}, countdown=15\n        )\n\n\n@instrumented_task(\n    name=\"sentry.tasks.deletion.delete_groups\",\n    queue=\"cleanup\",\n    default_retry_delay=60 * 5,\n    max_retries=MAX_RETRIES,\n)\n@retry(exclude=(DeleteAborted,))\n@track_group_async_operation\ndef delete_groups(object_ids, transaction_id=None, eventstream_state=None, **kwargs):\n    from sentry import deletions, eventstream\n    from sentry.models import Group\n\n    transaction_id = transaction_id or uuid4().hex\n\n    max_batch_size = 100\n    current_batch, rest = object_ids[:max_batch_size], object_ids[max_batch_size:]\n\n    task = deletions.get(\n        model=Group, query={\"id__in\": current_batch}, transaction_id=transaction_id\n    )\n    has_more = task.chunk()\n    if has_more or rest:\n        delete_groups.apply_async(\n            kwargs={\n                \"object_ids\": object_ids if has_more else rest,\n                \"transaction_id\": transaction_id,\n                \"eventstream_state\": eventstream_state,\n            },\n            countdown=15,\n        )\n    else:\n        # all groups have been deleted\n        if eventstream_state:\n            eventstream.end_delete_groups(eventstream_state)\n\n\n@instrumented_task(\n    name=\"sentry.tasks.deletion.delete_api_application\",\n    queue=\"cleanup\",\n    default_retry_delay=60 * 5,\n    max_retries=MAX_RETRIES,\n)\n@retry(exclude=(DeleteAborted,))\ndef delete_api_application(object_id, transaction_id=None, **kwargs):\n    from sentry import deletions\n    from sentry.models import ApiApplication, ApiApplicationStatus\n\n    try:\n        instance = ApiApplication.objects.get(id=object_id)\n    except ApiApplication.DoesNotExist:\n        return\n\n    if instance.status == ApiApplicationStatus.active:\n        raise DeleteAborted\n\n    task = deletions.get(\n        model=ApiApplication, query={\"id\": object_id}, transaction_id=transaction_id or uuid4().hex\n    )\n    has_more = task.chunk()\n    if has_more:\n        delete_api_application.apply_async(\n            kwargs={\"object_id\": object_id, \"transaction_id\": transaction_id}, countdown=15\n        )\n\n\n@instrumented_task(\n    name=\"sentry.tasks.deletion.generic_delete\",\n    queue=\"cleanup\",\n    default_retry_delay=60 * 5,\n    max_retries=MAX_RETRIES,\n)\n@retry(exclude=(DeleteAborted,))\ndef generic_delete(app_label, model_name, object_id, transaction_id=None, actor_id=None, **kwargs):\n    from sentry import deletions\n    from sentry.models import User\n\n    model = apps.get_model(app_label, model_name)\n\n    try:\n        instance = model.objects.get(id=object_id)\n    except model.DoesNotExist:\n        return\n\n    if instance.status != ObjectStatus.DELETION_IN_PROGRESS:\n        pending_delete.send(\n            sender=type(instance),\n            instance=instance,\n            actor=User.objects.get(id=actor_id) if actor_id else None,\n        )\n\n    if instance.status == ObjectStatus.VISIBLE:\n        raise DeleteAborted\n\n    task = deletions.get(\n        model=model,\n        actor_id=actor_id,\n        query={\"id\": object_id},\n        transaction_id=transaction_id or uuid4().hex,\n    )\n    has_more = task.chunk()\n    if has_more:\n        generic_delete.apply_async(\n            kwargs={\n                \"app_label\": app_label,\n                \"model_name\": model_name,\n                \"object_id\": object_id,\n                \"transaction_id\": transaction_id,\n                \"actor_id\": actor_id,\n            },\n            countdown=15,\n        )\n\n\n@instrumented_task(\n    name=\"sentry.tasks.deletion.delete_repository\",\n    queue=\"cleanup\",\n    default_retry_delay=60 * 5,\n    max_retries=MAX_RETRIES,\n)\n@retry(exclude=(DeleteAborted,))\ndef delete_repository(object_id, transaction_id=None, actor_id=None, **kwargs):\n    from sentry import deletions\n    from sentry.models import Repository, User\n\n    try:\n        instance = Repository.objects.get(id=object_id)\n    except Repository.DoesNotExist:\n        return\n\n    if instance.status == ObjectStatus.VISIBLE:\n        raise DeleteAborted\n\n    # compat: can be removed after we switch to scheduled deletions\n    if instance.status != ObjectStatus.DELETION_IN_PROGRESS:\n        pending_delete.send(\n            sender=type(instance),\n            instance=instance,\n            actor=User.objects.get(id=actor_id) if actor_id else None,\n        )\n\n    task = deletions.get(\n        model=Repository,\n        actor_id=actor_id,\n        query={\"id\": object_id},\n        transaction_id=transaction_id or uuid4().hex,\n    )\n    has_more = task.chunk()\n    if has_more:\n        delete_repository.apply_async(\n            kwargs={\"object_id\": object_id, \"transaction_id\": transaction_id, \"actor_id\": actor_id},\n            countdown=15,\n        )\n\n\n@instrumented_task(\n    name=\"sentry.tasks.deletion.delete_organization_integration\",\n    queue=\"cleanup\",\n    default_retry_delay=60 * 5,\n    max_retries=MAX_RETRIES,\n)\n@retry(exclude=(DeleteAborted,))\ndef delete_organization_integration(object_id, transaction_id=None, actor_id=None, **kwargs):\n    from sentry import deletions\n    from sentry.models import OrganizationIntegration, Repository\n\n    try:\n        instance = OrganizationIntegration.objects.get(id=object_id)\n    except OrganizationIntegration.DoesNotExist:\n        return\n\n    if instance.status == ObjectStatus.VISIBLE:\n        raise DeleteAborted\n\n    # dissociate repos from that integration\n    Repository.objects.filter(\n        organization_id=instance.organization_id, integration_id=instance.integration_id\n    ).update(integration_id=None)\n\n    task = deletions.get(\n        model=OrganizationIntegration,\n        actor_id=actor_id,\n        query={\"id\": object_id},\n        transaction_id=transaction_id or uuid4().hex,\n    )\n    has_more = task.chunk()\n    if has_more:\n        delete_organization_integration.apply_async(\n            kwargs={\"object_id\": object_id, \"transaction_id\": transaction_id, \"actor_id\": actor_id},\n            countdown=15,\n        )\n", "path": "src/sentry/tasks/deletion.py"}], "after_files": [{"content": "from __future__ import absolute_import\n\nfrom uuid import uuid4\n\nfrom django.apps import apps\nfrom django.conf import settings\nfrom django.db import transaction\nfrom django.utils import timezone\n\nfrom sentry.constants import ObjectStatus\nfrom sentry.exceptions import DeleteAborted\nfrom sentry.signals import pending_delete\nfrom sentry.tasks.base import instrumented_task, retry, track_group_async_operation\n\n# in prod we run with infinite retries to recover from errors\n# in debug/development, we assume these tasks generally shouldn't fail\nMAX_RETRIES = 1 if settings.DEBUG else 5\n\n\n@instrumented_task(name=\"sentry.tasks.deletion.run_scheduled_deletions\", queue=\"cleanup\")\ndef run_scheduled_deletions():\n    from sentry.models import ScheduledDeletion\n\n    queryset = ScheduledDeletion.objects.filter(\n        in_progress=False, aborted=False, date_scheduled__lte=timezone.now()\n    )\n    for item in queryset:\n        with transaction.atomic():\n            affected = ScheduledDeletion.objects.filter(\n                id=item.id, in_progress=False, aborted=False\n            ).update(in_progress=True)\n            if not affected:\n                continue\n\n            run_deletion.delay(deletion_id=item.id)\n\n\n@instrumented_task(\n    name=\"sentry.tasks.deletion.run_deletion\",\n    queue=\"cleanup\",\n    default_retry_delay=60 * 5,\n    max_retries=MAX_RETRIES,\n)\n@retry(exclude=(DeleteAborted,))\ndef run_deletion(deletion_id):\n    from sentry import deletions\n    from sentry.models import ScheduledDeletion\n\n    try:\n        deletion = ScheduledDeletion.objects.get(id=deletion_id)\n    except ScheduledDeletion.DoesNotExist:\n        return\n\n    if deletion.aborted:\n        raise DeleteAborted\n\n    if not deletion.in_progress:\n        actor = deletion.get_actor()\n        instance = deletion.get_instance()\n        with transaction.atomic():\n            deletion.update(in_progress=True)\n            pending_delete.send(sender=type(instance), instance=instance, actor=actor)\n\n    task = deletions.get(\n        model=deletion.get_model(),\n        query={\"id\": deletion.object_id},\n        transaction_id=deletion.guid,\n        actor_id=deletion.actor_id,\n    )\n    has_more = task.chunk()\n    if has_more:\n        run_deletion.apply_async(kwargs={\"deletion_id\": deletion_id}, countdown=15)\n    deletion.delete()\n\n\n@instrumented_task(\n    name=\"sentry.tasks.deletion.revoke_api_tokens\",\n    queue=\"cleanup\",\n    default_retry_delay=60 * 5,\n    max_retries=MAX_RETRIES,\n)\n@retry(exclude=(DeleteAborted,))\ndef revoke_api_tokens(object_id, transaction_id=None, timestamp=None, **kwargs):\n    from sentry.models import ApiToken\n\n    queryset = ApiToken.objects.filter(application=object_id)\n    if timestamp:\n        queryset = queryset.filter(date_added__lte=timestamp)\n\n    # we're using a slow deletion strategy to avoid a lot of custom code for\n    # postgres\n    has_more = False\n    for obj in queryset[:1000]:\n        obj.delete()\n        has_more = True\n\n    if has_more:\n        revoke_api_tokens.apply_async(\n            kwargs={\n                \"object_id\": object_id,\n                \"transaction_id\": transaction_id,\n                \"timestamp\": timestamp,\n            },\n            countdown=15,\n        )\n    return has_more\n\n\n@instrumented_task(\n    name=\"sentry.tasks.deletion.delete_organization\",\n    queue=\"cleanup\",\n    default_retry_delay=60 * 5,\n    max_retries=MAX_RETRIES,\n)\n@retry(exclude=(DeleteAborted,))\ndef delete_organization(object_id, transaction_id=None, actor_id=None, **kwargs):\n    from sentry import deletions\n    from sentry.models import Organization, OrganizationStatus\n\n    try:\n        instance = Organization.objects.get(id=object_id)\n    except Organization.DoesNotExist:\n        return\n\n    if instance.status == OrganizationStatus.VISIBLE:\n        raise DeleteAborted\n\n    # compat: can be removed after we switch to scheduled deletions\n    if instance.status != OrganizationStatus.DELETION_IN_PROGRESS:\n        pending_delete.send(sender=type(instance), instance=instance)\n\n    task = deletions.get(\n        model=Organization,\n        query={\"id\": object_id},\n        transaction_id=transaction_id or uuid4().hex,\n        actor_id=actor_id,\n    )\n    has_more = task.chunk()\n    if has_more:\n        delete_organization.apply_async(\n            kwargs={\"object_id\": object_id, \"transaction_id\": transaction_id, \"actor_id\": actor_id},\n            countdown=15,\n        )\n\n\n@instrumented_task(\n    name=\"sentry.tasks.deletion.delete_team\",\n    queue=\"cleanup\",\n    default_retry_delay=60 * 5,\n    max_retries=MAX_RETRIES,\n)\n@retry(exclude=(DeleteAborted,))\ndef delete_team(object_id, transaction_id=None, **kwargs):\n    from sentry import deletions\n    from sentry.models import Team, TeamStatus\n\n    try:\n        instance = Team.objects.get(id=object_id)\n    except Team.DoesNotExist:\n        return\n\n    if instance.status == TeamStatus.VISIBLE:\n        raise DeleteAborted\n\n    task = deletions.get(\n        model=Team, query={\"id\": object_id}, transaction_id=transaction_id or uuid4().hex\n    )\n    has_more = task.chunk()\n    if has_more:\n        delete_team.apply_async(\n            kwargs={\"object_id\": object_id, \"transaction_id\": transaction_id}, countdown=15\n        )\n\n\n@instrumented_task(\n    name=\"sentry.tasks.deletion.delete_project\",\n    queue=\"cleanup\",\n    default_retry_delay=60 * 5,\n    max_retries=MAX_RETRIES,\n)\n@retry(exclude=(DeleteAborted,))\ndef delete_project(object_id, transaction_id=None, **kwargs):\n    from sentry import deletions\n    from sentry.models import Project, ProjectStatus\n\n    try:\n        instance = Project.objects.get(id=object_id)\n    except Project.DoesNotExist:\n        return\n\n    if instance.status == ProjectStatus.VISIBLE:\n        raise DeleteAborted\n\n    task = deletions.get(\n        model=Project, query={\"id\": object_id}, transaction_id=transaction_id or uuid4().hex\n    )\n    has_more = task.chunk()\n    if has_more:\n        delete_project.apply_async(\n            kwargs={\"object_id\": object_id, \"transaction_id\": transaction_id}, countdown=15\n        )\n\n\n@instrumented_task(\n    name=\"sentry.tasks.deletion.delete_groups\",\n    queue=\"cleanup\",\n    default_retry_delay=60 * 5,\n    max_retries=MAX_RETRIES,\n)\n@retry(exclude=(DeleteAborted,))\n@track_group_async_operation\ndef delete_groups(object_ids, transaction_id=None, eventstream_state=None, **kwargs):\n    from sentry import deletions, eventstream\n    from sentry.models import Group\n\n    transaction_id = transaction_id or uuid4().hex\n\n    max_batch_size = 100\n    current_batch, rest = object_ids[:max_batch_size], object_ids[max_batch_size:]\n\n    task = deletions.get(\n        model=Group, query={\"id__in\": current_batch}, transaction_id=transaction_id\n    )\n    has_more = task.chunk()\n    if has_more or rest:\n        delete_groups.apply_async(\n            kwargs={\n                \"object_ids\": object_ids if has_more else rest,\n                \"transaction_id\": transaction_id,\n                \"eventstream_state\": eventstream_state,\n            },\n            countdown=15,\n        )\n    else:\n        # all groups have been deleted\n        if eventstream_state:\n            eventstream.end_delete_groups(eventstream_state)\n\n\n@instrumented_task(\n    name=\"sentry.tasks.deletion.delete_api_application\",\n    queue=\"cleanup\",\n    default_retry_delay=60 * 5,\n    max_retries=MAX_RETRIES,\n)\n@retry(exclude=(DeleteAborted,))\ndef delete_api_application(object_id, transaction_id=None, **kwargs):\n    from sentry import deletions\n    from sentry.models import ApiApplication, ApiApplicationStatus\n\n    try:\n        instance = ApiApplication.objects.get(id=object_id)\n    except ApiApplication.DoesNotExist:\n        return\n\n    if instance.status == ApiApplicationStatus.active:\n        raise DeleteAborted\n\n    task = deletions.get(\n        model=ApiApplication, query={\"id\": object_id}, transaction_id=transaction_id or uuid4().hex\n    )\n    has_more = task.chunk()\n    if has_more:\n        delete_api_application.apply_async(\n            kwargs={\"object_id\": object_id, \"transaction_id\": transaction_id}, countdown=15\n        )\n\n\n@instrumented_task(\n    name=\"sentry.tasks.deletion.generic_delete\",\n    queue=\"cleanup\",\n    default_retry_delay=60 * 5,\n    max_retries=MAX_RETRIES,\n)\n@retry(exclude=(DeleteAborted,))\ndef generic_delete(app_label, model_name, object_id, transaction_id=None, actor_id=None, **kwargs):\n    from sentry import deletions\n    from sentry.models import User\n\n    model = apps.get_model(app_label, model_name)\n\n    try:\n        instance = model.objects.get(id=object_id)\n    except model.DoesNotExist:\n        return\n\n    if instance.status != ObjectStatus.DELETION_IN_PROGRESS:\n        pending_delete.send(\n            sender=type(instance),\n            instance=instance,\n            actor=User.objects.get(id=actor_id) if actor_id else None,\n        )\n\n    if instance.status == ObjectStatus.VISIBLE:\n        raise DeleteAborted\n\n    task = deletions.get(\n        model=model,\n        actor_id=actor_id,\n        query={\"id\": object_id},\n        transaction_id=transaction_id or uuid4().hex,\n    )\n    has_more = task.chunk()\n    if has_more:\n        generic_delete.apply_async(\n            kwargs={\n                \"app_label\": app_label,\n                \"model_name\": model_name,\n                \"object_id\": object_id,\n                \"transaction_id\": transaction_id,\n                \"actor_id\": actor_id,\n            },\n            countdown=15,\n        )\n\n\n@instrumented_task(\n    name=\"sentry.tasks.deletion.delete_repository\",\n    queue=\"cleanup\",\n    default_retry_delay=60 * 5,\n    max_retries=MAX_RETRIES,\n)\n@retry(exclude=(DeleteAborted,))\ndef delete_repository(object_id, transaction_id=None, actor_id=None, **kwargs):\n    from sentry import deletions\n    from sentry.models import Repository, User\n\n    try:\n        instance = Repository.objects.get(id=object_id)\n    except Repository.DoesNotExist:\n        return\n\n    if instance.status == ObjectStatus.VISIBLE:\n        raise DeleteAborted\n\n    # compat: can be removed after we switch to scheduled deletions\n    if instance.status != ObjectStatus.DELETION_IN_PROGRESS:\n        pending_delete.send(\n            sender=type(instance),\n            instance=instance,\n            actor=User.objects.get(id=actor_id) if actor_id else None,\n        )\n\n    task = deletions.get(\n        model=Repository,\n        actor_id=actor_id,\n        query={\"id\": object_id},\n        transaction_id=transaction_id or uuid4().hex,\n    )\n    has_more = task.chunk()\n    if has_more:\n        delete_repository.apply_async(\n            kwargs={\"object_id\": object_id, \"transaction_id\": transaction_id, \"actor_id\": actor_id},\n            countdown=15,\n        )\n\n\n@instrumented_task(\n    name=\"sentry.tasks.deletion.delete_organization_integration\",\n    queue=\"cleanup\",\n    default_retry_delay=60 * 5,\n    max_retries=MAX_RETRIES,\n)\n@retry(exclude=(DeleteAborted,))\ndef delete_organization_integration(object_id, transaction_id=None, actor_id=None, **kwargs):\n    from sentry import deletions\n    from sentry.models import OrganizationIntegration, Repository\n\n    try:\n        instance = OrganizationIntegration.objects.get(id=object_id)\n    except OrganizationIntegration.DoesNotExist:\n        return\n\n    if instance.status == ObjectStatus.VISIBLE:\n        raise DeleteAborted\n\n    # dissociate repos from that integration\n    Repository.objects.filter(\n        organization_id=instance.organization_id, integration_id=instance.integration_id\n    ).update(integration_id=None)\n\n    task = deletions.get(\n        model=OrganizationIntegration,\n        actor_id=actor_id,\n        query={\"id\": object_id},\n        transaction_id=transaction_id or uuid4().hex,\n    )\n    has_more = task.chunk()\n    if has_more:\n        delete_organization_integration.apply_async(\n            kwargs={\"object_id\": object_id, \"transaction_id\": transaction_id, \"actor_id\": actor_id},\n            countdown=15,\n        )\n", "path": "src/sentry/tasks/deletion.py"}]}