Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Propagates more ImportErrors during autodiscovery #8632

Conversation

johnjameswhitman
Copy link
Contributor

@johnjameswhitman johnjameswhitman commented Nov 10, 2023

Problem

The gist of the problem is that the current implementation of autodiscovering tasks will hide ImportErrors that originate outside of importlib.import_module.

For details see discussion in #8620.

Solution

Narrow the caught-exceptions to ones that:

  1. Are ModuleNotFoundError (which is a subclass of ImportError that importlib seems to raise these days), and
  2. Have a name attribute that matches the task being "guessed" (previously the code would default to the task's name if the exception lacked a name attribute).

Also, refactored the tests into what seem like the specific edge cases find_related_module is trying to handle.

@@ -253,20 +253,26 @@ def find_related_module(package, related_name):
# Django 1.7 allows for specifying a class name in INSTALLED_APPS.
# (Issue #2248).
try:
# Return package itself when no related_name.
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I dropped some notes to myself in these comments to try and figure out what each of these conditions was trying to do. Happy to remove if you don't want to clutter the code.

Comment on lines 277 to 284
def test_find_related_module__when_existent_package_parent_but_not_related_name(self):
with patch('importlib.import_module') as imp:
first_import = ModuleNotFoundError(name='foo.bar')
second_import = ModuleNotFoundError(name='foo.tasks')
imp.side_effect = [first_import, second_import]
assert base.find_related_module('foo.bar', 'tasks') is None
imp.assert_any_call('foo.bar')
imp.assert_any_call('foo.tasks')
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a weird case -- I'm not sure if find_related_module is actually meant to handle this or not, so can remove it if not.


def test_find_related_module__when_existent_package_parent_and_related_name(self):
with patch('importlib.import_module') as imp:
first_import = ModuleNotFoundError(name='foo.BarApp') # Ref issue #2248
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It seems like this behavior was added to match some edge case w/ how Django apps present themselves for auto-discovery (see #2248).

Copy link

codecov bot commented Nov 10, 2023

Codecov Report

All modified and coverable lines are covered by tests ✅

Comparison is base (3723009) 87.33% compared to head (347b310) 87.34%.
Report is 14 commits behind head on main.

Additional details and impacted files
@@            Coverage Diff             @@
##             main    #8632      +/-   ##
==========================================
+ Coverage   87.33%   87.34%   +0.01%     
==========================================
  Files         148      148              
  Lines       18515    18519       +4     
  Branches     3163     3164       +1     
==========================================
+ Hits        16170    16176       +6     
+ Misses       2060     2059       -1     
+ Partials      285      284       -1     
Flag Coverage Δ
unittests 87.31% <100.00%> (+0.01%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

@auvipy auvipy requested review from auvipy and a team November 11, 2023 05:30
Copy link
Member

@auvipy auvipy left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

thanks for handling this and digging deep into the code history! we will wait to have review/feedback from several maintainers so it might need some more time then expected.

In the mean time, it would be great if you can figure out missing test coverage or improve it further. I'm not sure this part of code has Integration test or not and if it is possible to add Intg tests or not. If you can check that can be added it will be really awesome

@auvipy auvipy added this to the 5.4 milestone Nov 12, 2023
@johnjameswhitman
Copy link
Contributor Author

Fixed the coverage issue. I don't think I saw integration tests but will double-check.

@auvipy
Copy link
Member

auvipy commented Nov 14, 2023

CI failures are network issues. pypy passed after retry. now retried cpython3.12 again

@auvipy auvipy modified the milestones: 5.4, 5.3.x Nov 14, 2023
@auvipy auvipy mentioned this pull request Nov 16, 2023
10 tasks
import_exc_name = getattr(e, 'name', module_name)
if import_exc_name is not None and import_exc_name != module_name:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can we make sure this change / removal won't create any regression?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will do -- I should have some more time to look at this next week.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

will appreciate this to be done today.... but if you need some more time that is completely fine and we can merge this after 5.3.6 release of celery?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Dropped an integration test in -- LMK how that looks 🙏

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

that look good!! thanks

def test_autodiscovery(self, manager):
# Arrange
expected_package_name, _, module_name = __name__.rpartition('.')
unexpected_package_name = 'nonexistent.package.name'
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This actually caught a bug I fixed in 3e405b1.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

thanks!

Copy link
Member

@auvipy auvipy left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can you please take a look into the following test failure? although I will try too re run the test to see if it pass. but fixing it would be great

=================================== FAILURES ===================================
________________________ test_tasks.test_task_accepted _________________________

self = <t.integration.test_tasks.test_tasks object at 0x7fc7c05b21c0>
manager = <celery.contrib.testing.manager.Manager object at 0x7fc7be18f820>
sleep = 1

  @flaky
  def test_task_accepted(self, manager, sleep=1):
      r1 = sleeping.delay(sleep)
      sleeping.delay(sleep)
  manager.assert_accepted([r1.id])

t/integration/test_tasks.py:409:


celery/contrib/testing/manager.py:150: in assert_accepted
return self.assert_task_worker_state(
celery/contrib/testing/manager.py:186: in assert_task_worker_state
return self.wait_for(
celery/contrib/testing/manager.py:81: in wait_for
return self.retry_over_time(
celery/contrib/testing/manager.py:107: in retry_over_time
return retry_over_time(*args, **kwargs)
.tox/3.8-integration-redis/lib/python3.8/site-packages/kombu/utils/functional.py:318: in retry_over_time
return fun(*args, **kwargs)


self = <celery.contrib.testing.manager.Manager object at 0x7fc7be18f820>
fun = <bound method ManagerMixin.is_accepted of <celery.contrib.testing.manager.Manager object at 0x7fc7be18f820>>
args = (['267f60e6-783a-49c5-a41f-4b34ec2f2908'],), kwargs = {'timeout': 0.5}
res = False

  def true_or_raise(self, fun, *args, **kwargs):
      res = fun(*args, **kwargs)
      if not res:
      raise Sentinel()

E celery.contrib.testing.manager.Sentinel

celery/contrib/testing/manager.py:208: Sentinel
------------------------------ Captured log call -------------------------------
INFO celery.worker.strategy:strategy.py:161 Task t.integration.tasks.sleeping[c3ad399a-a7b8-4766-a0c7-6d58201187f7] received
INFO celery.worker.strategy:strategy.py:161 Task t.integration.tasks.sleeping[fd71157f-8c08-4b28-81c9-9da73a18f9e5] received
INFO celery.app.trace:trace.py:131 Task t.integration.tasks.sleeping[c3ad399a-a7b8-4766-a0c7-6d58201187f7] succeeded in 1.0021785389999422s: None
INFO celery.app.trace:trace.py:131 Task t.integration.tasks.sleeping[fd71157f-8c08-4b28-81c9-9da73a18f9e5] succeeded in 1.0021406060000118s: None
INFO celery.worker.request:request.py:496 Discarding revoked task: t.integration.tasks.retry_unpickleable[66fecb16-57d6-4a53-9c06-9c457ccf3cb5]
------------------------------ Captured log call -------------------------------
INFO celery.worker.strategy:strategy.py:161 Task t.integration.tasks.sleeping[761953e3-17ce-4461-9f90-d0e6faf3a316] received
INFO celery.worker.strategy:strategy.py:161 Task t.integration.tasks.sleeping[97c69a23-d98e-456d-8043-4ea0b365a7aa] received
INFO celery.app.trace:trace.py:131 Task t.integration.tasks.sleeping[761953e3-17ce-4461-9f90-d0e6faf3a316] succeeded in 1.0021477600000708s: None
INFO celery.app.trace:trace.py:131 Task t.integration.tasks.sleeping[97c69a23-d98e-456d-8043-4ea0b365a7aa] succeeded in 1.0022280179998688s: None
------------------------------ Captured log call -------------------------------
INFO celery.worker.strategy:strategy.py:161 Task t.integration.tasks.sleeping[e5eb6331-eb5e-453e-bae0-aa9165ce2dc5] received
INFO celery.worker.strategy:strategy.py:161 Task t.integration.tasks.sleeping[17e36c16-8f74-415c-ba83-a98bda23d729] received
INFO celery.app.trace:trace.py:131 Task t.integration.tasks.sleeping[e5eb6331-eb5e-453e-bae0-aa9165ce2dc5] succeeded in 1.0022641999998996s: None
INFO celery.app.trace:trace.py:131 Task t.integration.tasks.sleeping[17e36c16-8f74-415c-ba83-a98bda23d729] succeeded in 1.0021502090000922s: None
------------------------------ Captured log call -------------------------------
INFO celery.worker.strategy:strategy.py:161 Task t.integration.tasks.sleeping[85f129f4-0425-4778-952e-907b38940e41] received
INFO celery.worker.strategy:strategy.py:161 Task t.integration.tasks.sleeping[44e5e5e1-170d-418d-97fa-21d813ed584c] received
INFO celery.app.trace:trace.py:131 Task t.integration.tasks.sleeping[85f129f4-0425-4778-952e-907b38940e41] succeeded in 1.0023080529999788s: None
INFO celery.app.trace:trace.py:131 Task t.integration.tasks.sleeping[44e5e5e1-170d-418d-97fa-21d813ed584c] succeeded in 1.0021153309999136s: None
------------------------------ Captured log call -------------------------------
INFO celery.worker.strategy:strategy.py:161 Task t.integration.tasks.sleeping[1f82ba9c-792d-4bad-bf0a-1403836fde26] received
INFO celery.worker.strategy:strategy.py:161 Task t.integration.tasks.sleeping[ae43d6b8-adfe-47b7-8caf-6d58c02ef704] received
INFO celery.app.trace:trace.py:131 Task t.integration.tasks.sleeping[1f82ba9c-792d-4bad-bf0a-1403836fde26] succeeded in 1.0012457650000215s: None
INFO celery.app.trace:trace.py:131 Task t.integration.tasks.sleeping[ae43d6b8-adfe-47b7-8caf-6d58c02ef704] succeeded in 1.001963686000181s: None
------------------------------ Captured log call -------------------------------
INFO celery.worker.strategy:strategy.py:161 Task t.integration.tasks.sleeping[267f60e6-783a-49c5-a41f-4b34ec2f2908] received
INFO celery.worker.strategy:strategy.py:161 Task t.integration.tasks.sleeping[74b0356c-02c3-470e-a1c7-bec26c9fde5b] received
INFO celery.app.trace:trace.py:131 Task t.integration.tasks.sleeping[267f60e6-783a-49c5-a41f-4b34ec2f2908] succeeded in 1.002038981999931s: None
INFO celery.app.trace:trace.py:131 Task t.integration.tasks.sleeping[74b0356c-02c3-470e-a1c7-bec26c9fde5b] succeeded in 1.0021865470000648s: None
=============================== warnings summary ===============================
t/integration/test_canvas.py: 39 warnings
/home/runner/work/celery/celery/celery/canvas.py:2283: CPendingDeprecationWarning: task_allow_error_cb_on_chord_header=False is pending deprecation in a future release of Celery.
Please test the new behavior by setting task_allow_error_cb_on_chord_header to True and report any concerns you might have in our issue tracker before we make a final decision regarding how errbacks should behave when used with chords.
warnings.warn(

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED t/integration/test_tasks.py::test_tasks::test_task_accepted - celery.c...

@auvipy auvipy merged commit 3ba50e4 into celery:main Nov 21, 2023
32 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
Status: Done
Development

Successfully merging this pull request may close these issues.

None yet

2 participants