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

Fix wrong import logic when loading modules #140

Closed
wants to merge 2 commits into from

Conversation

blackthunder0812
Copy link

Only load module within package, not all accessible modules

Loading all accessible modules causes a bug when an accessible module has a same name with module inside package

Proposed changes:

  • Only load module within package, not all accessible modules

Status (please check what you already did):

  • made PR ready for code review
  • added some tests for the functionality
  • updated the documentation
  • updated the changelog
  • reformat files using black (please check Readme for instructions)

Only load module within package, not all accessible modules

Loading all accessible modules causes a bug when an accessible module has a same name with module inside package
@wochinge
Copy link
Contributor

@blackthunder0812 Thanks for your contribution! 🎉 Could you please elaborate a bit on the bug so that I can reproduce it?

@tmbo tmbo removed request for tmbo and ricwo January 22, 2020 16:57
@blackthunder0812
Copy link
Author

blackthunder0812 commented Jan 24, 2020

@wochinge For example, if we have a project structure like this:

$ tree
.
├── actions
│   └── utils
│       └── module1.py
└── utils
    └── module2.py

Then action server cannot load the module inside actions/utils folder because it already loads modules in utils forder, sample error log:

Nov 29, 2019 @ 15:04:10.2492019-11-29 15:04:10 ERROR    rasa_sdk.executor  - Failed to register package 'actions'.
Nov 29, 2019 @ 15:04:10.249Traceback (most recent call last):
Nov 29, 2019 @ 15:04:10.249  File "/app/rasa_sdk/executor.py", line 175, in register_package
Nov 29, 2019 @ 15:04:10.249    self._import_submodules(package)
Nov 29, 2019 @ 15:04:10.249  File "/app/rasa_sdk/executor.py", line 167, in _import_submodules
Nov 29, 2019 @ 15:04:10.249    results[full_name] = importlib.import_module(full_name)
Nov 29, 2019 @ 15:04:10.249  File "/usr/local/lib/python3.6/importlib/__init__.py", line 126, in import_module
Nov 29, 2019 @ 15:04:10.249    return _bootstrap._gcd_import(name[level:], package, level)
Nov 29, 2019 @ 15:04:10.249  File "<frozen importlib._bootstrap>", line 994, in _gcd_import
Nov 29, 2019 @ 15:04:10.249  File "<frozen importlib._bootstrap>", line 971, in _find_and_load
Nov 29, 2019 @ 15:04:10.249  File "<frozen importlib._bootstrap>", line 953, in _find_and_load_unlocked
	Nov 29, 2019 @ 15:04:10.249
ModuleNotFoundError: No module named 'actions.utils.chatette_fix'

Reproducing bug with above project structure:

for loader, name, is_pkg in pkgutil.walk_packages(package.__path__):
    full_name = package.__name__ + "." + name
    results[full_name] = importlib.import_module(full_name)

Because we have 2 directories with the same name utils (/utils and /actions/utils), function pkgutil.walk_packages incorrectly yield packages in /utils directory:

>>> package = importlib.import_module('actions')
DEBUG:actions:LOADED CONFIG BOT
>>> pprint.pprint([(loader, name, is_pkg) for loader, name, is_pkg in pkgutil.walk_packages(package.__path__)])
[(FileFinder('/app/actions'), 'default_ask_affirmation', False),
 (FileFinder('/app/actions'), 'fee_ship', False),
 (FileFinder('/app/actions'), 'find_product', False),
 (FileFinder('/app/actions'), 'find_product_by_category', False),
 (FileFinder('/app/actions'), 'graphdb', True),
 (FileFinder('/app/actions'), 'handover_to_inbox', False),
 (FileFinder('/app/actions'), 'order_product', False),
 (FileFinder('/app/actions'), 'order_product_form', False),
 (FileFinder('/app/actions'), 'query_kb', False),
 (FileFinder('/app/actions'), 'response_total_cost', False),
 (FileFinder('/app/actions'), 'search_engine', True),
 (FileFinder('/app/actions'), 'start_conversation', False),
 (FileFinder('/app/actions'), 'utils', True),
 (FileFinder('/app/utils'), 'utils.chatette_fix', False),
 (FileFinder('/app/utils'), 'utils.facebook_utils', False),
 (FileFinder('/app/utils'), 'utils.log_config', False),
 (FileFinder('/app/utils'), 'utils.lookup_tables', False)]

Then we can fix by loading only modules inside packages, recursively, don't load all accessible modules

Copy link
Contributor

@wochinge wochinge left a comment

Choose a reason for hiding this comment

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

@blackthunder0812

I tried with this structure

.
└── test
    ├── __init__.py
    ├── actions.py . # imports module1 and module2
    ├── other
    │   ├── __init__.py
    │   └── utils
    │       ├── __init__.py
    │       └── module1.py
    └── utils
        ├── __init__.py
        └── module2.py

and running rasa run actions --actions test which works fine. When I use your fix it actually breaks (gettig ModuleNotFoundError: No module named 'test.testactions') .

Which command are you using to run the action server and did you include all the __init__ files to mark the directories as Python packages?

@blackthunder0812
Copy link
Author

@blackthunder0812

I tried with this structure

.
└── test
    ├── __init__.py
    ├── actions.py . # imports module1 and module2
    ├── other
    │   ├── __init__.py
    │   └── utils
    │       ├── __init__.py
    │       └── module1.py
    └── utils
        ├── __init__.py
        └── module2.py

and running rasa run actions --actions test which works fine. When I use your fix it actually breaks (gettig ModuleNotFoundError: No module named 'test.testactions') .

Which command are you using to run the action server and did you include all the __init__ files to mark the directories as Python packages?

Thanks for pointing out the problem.

My project structure is different, please look at images below, (actions folder and utils folder are at same level) this is before patch:

Screenshot from 2020-02-04 15-31-00

This is after new patch:

Screenshot from 2020-02-04 15-38-37

@blackthunder0812
Copy link
Author

It works even if I remove all __init__.py files

Screenshot from 2020-02-04 15-54-58

@blackthunder0812
Copy link
Author

@wochinge Can you please try again?

@wochinge
Copy link
Contributor

@blackthunder0812 I tried again, and your change works, but

  • it also works with the old code for me
  • seems like it's traversing some things multiple times with your code.

I adapted my structure to yours:

.
├── __init__.py
├── actions.py
├── other
│   ├── __init__.py
│   └── utils
│       ├── __init__.py
│       └── module1.py
└── utils
    ├── __init__.py
    └── module2.py

And then run rasa run actions --actions actions`` where actions.py` imports

from utils.module2 import MODULE_2
from other.utils.module1 import MODULE_1

Works like a charm

@blackthunder0812
Copy link
Author

blackthunder0812 commented Feb 17, 2020

@wochinge

The problem does not lie on importing module in actions.py file, but in importing modules from loading modules in action. To reproduce problem, construct a project structure as following:

.
├── actions
│   ├── actions.py
│   └── utils
│       ├── __init__.py
│       └── module2.py
├── __init__.py
└── utils
    ├── __init__.py
    └── module1.py

Then run:

rasa run actions --actions actions

with actions.py is empty.

Before patching:

2020-02-17 17:51:23 INFO     rasa_sdk.endpoint  - Starting action endpoint server...
2020-02-17 17:51:23 ERROR    rasa_sdk.executor  - Failed to register package 'actions'.
Traceback (most recent call last):
  File "/home/sonbn/Workspace/venvs/rasa-dev/lib/python3.6/site-packages/rasa_sdk/executor.py", line 206, in register_package
    self._import_submodules(package)
  File "/home/sonbn/Workspace/venvs/rasa-dev/lib/python3.6/site-packages/rasa_sdk/executor.py", line 198, in _import_submodules
    results[full_name] = importlib.import_module(full_name)
  File "/usr/lib/python3.6/importlib/__init__.py", line 126, in import_module
    return _bootstrap._gcd_import(name[level:], package, level)
  File "<frozen importlib._bootstrap>", line 994, in _gcd_import
  File "<frozen importlib._bootstrap>", line 971, in _find_and_load
  File "<frozen importlib._bootstrap>", line 953, in _find_and_load_unlocked
ModuleNotFoundError: No module named 'actions.utils.module1'

Please look at screenshot below

Screenshot from 2020-02-17 17-55-18

The difference between ours is that my --actions flag point to actions folder, which is a package, not actions.py file

@wochinge
Copy link
Contributor

wochinge commented Mar 5, 2020

The problem does not lie on importing module in actions.py file, but in importing modules from loading modules in action. To reproduce problem, construct a project structure as following:

In my opinion the error in your current directory structure makes sense. You are importing the actions module which references a package which is not in your PYTHONPATH. In my opinion utils (the one including module 1) should be part of your actions module or you have to add it to your PYTHONPATH.

@blackthunder0812
Copy link
Author

But I don't actually import anything, please note that all files are empty. Then the problem is that even if I don't import any module at outer utils folder, action server will still crash.

The project structure as above is quite popular and should be valid I think.

@wochinge
Copy link
Contributor

But I don't actually import anything, please note that all files are empty.

Even if the files are empty, you're still importing a module (even if it's empty), no?

Then the problem is that even if I don't import any module at outer utils folder, action server will still crash.

Can you maybe upload zip for me so that I can reproduce it? I think that would make it easier 🙏

@blackthunder0812
Copy link
Author

@wochinge Please check, test command:

python -m rasa_sdk --actions actions -v -vv

actions.zip

@wochinge
Copy link
Contributor

wochinge commented May 19, 2020

Sorry, for the super long delay @blackthunder0812 . Finally I could re-produce the problem 👍 🙏

Two things:

  • your solution seems to add a couple of round trips to the importing. _import_submodules seems to be called repeatedly on the same thing. I tried a different solution which simply skips loading which are modules above the directory of the current package. Please let me know if you can see any downsides with this approach.
for loader, name, is_pkg in pkgutil.walk_packages(package.__path__):
    if loader.path not in package.__path__:
        continue
   
   # rest of the code
  • Could you please add a test for the fix? E.g. re-recreating the file structure which you sent in the actions.zip and then trying to import the submodules from it

Thanks again for being so patient!

@CLAassistant
Copy link

CLA assistant check
Thank you for your submission! We really appreciate it. Like many open source projects, we ask that you sign our Contributor License Agreement before we can accept your contribution.
You have signed the CLA already but the status is still pending? Let us recheck it.

@wochinge wochinge closed this Nov 13, 2020
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants