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

[ADD] hooks: New unused python file check #61

Closed
wants to merge 1 commit into from

Conversation

antonag32
Copy link
Contributor

Unused python files are considered python files not imported in their corresponding init.py file. This only applies to actual files that need to be imported, as it only checks python files in standard locations that must be imported by odoo. This includes the following folders:

  • controllers
  • models
  • report
  • wizard
  • tests (only if it starts with test_)

Related to: Vauxoo/pylint-odoo#177

I decided against placing this checker in pylint since the entry point for it would be ugly and probably involve non standard code (aka can't be easily done with visit_ functions). I also did not introduce this into an existing hook since its correct functioning depends on the right files setting on pre-commit-hooks.yaml.

The code structure does not fill very well with existing code, but I think it might fit well after a restructuring, and I personally believe this is the most correct way to implement the desired check.

When run against Odoo 16.0 I get the following output:

Check unused python files................................................Failed
- hook id: oca-check-unused-python-file
- exit code: 255

addons/microsoft_calendar/tests/test_sync_odoo2microsoft.py: not imported
addons/microsoft_calendar/tests/test_sync_microsoft2odoo.py: not imported
odoo/addons/base/tests/test_uninstall.py: not imported
addons/lunch/tests/test_ui.py: not imported
odoo/tests/test_module_operations.py: not imported
odoo/addons/base/tests/test_mail_examples.py: not imported
addons/l10n_it_edi/tests/test_res_partner.py: not imported

I don't know if Odoo did that on purpose but if they were planning on those tests being run, they won't, at least according to their documentation.

@antonag32 antonag32 force-pushed the unused-python-file-anton branch 3 times, most recently from ac1b381 to faeb201 Compare November 11, 2022 22:29
Unused python files are considered python files not imported in
their corresponding __init__.py file. This only applies to
actual files that need to be imported, and automatically excludes
folders like migration or __manifest__.py files
@moylop260
Copy link
Collaborator

I think the best place to create python lints is pylint

It could be done using def open or using def visit_module in order to get all imported nodes for a particular module directory

Could we discuss it, please?

entry: oca-check-unused-python-file
language: python
types: [python]
files: (controllers|models|report|wizard)\.py$|tests\/test_.*\.py$
Copy link
Collaborator

Choose a reason for hiding this comment

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

We have hooks.py not considered here

There are cases where the developer use plural or singular names or even use the root path of the module to create files directly

Only be aware to discard known cases:

  • __init__.py
  • migrations/

Copy link
Contributor Author

Choose a reason for hiding this comment

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

True, I missed hooks.py.

Initially I went with the exclude route, only excluding __init__.py, migrations/ and __manifest__.py, but if you run the hook against Odoo, you will find there are tons of false positives, for example the drivers for IoT are python files and I am not sure they need to be imported, and they are also in non standard paths.

https://github.com/odoo/odoo/blob/16.0/addons/hw_drivers/iot_handlers/drivers/KeyboardUSBDriver.py

That is just an example, so I decided to instead just focus on well known folders that we know you must include all files from, which feels cleaner than excluding a few well known folders that we know you don't have to include files from.

return [f"{os.path.join(basedir, filename)}.py" for filename in filenames]


def test_all_used_files(tmpdir):
Copy link
Collaborator

Choose a reason for hiding this comment

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

Why are you not using unittest?

Could you use it?

Copy link
Collaborator

Choose a reason for hiding this comment

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

Copy link
Collaborator

Choose a reason for hiding this comment

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

BTW I didn't find where tmpdir variable is assigned

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Since we are using pytest I did not find it necessary to use the old unittest way. Using a setUp and tearDown is not necessary since pytest already does this for us, that is why I am using tmpdir which automatically creates a temporary directory, that option is deprecated tho so I need to change it to tmp_path.

https://docs.pytest.org/en/7.1.x/how-to/tmp_path.html

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Also, I find it cleaner to separate test files per functionality being tested instead of placing all tests in the same file so I placed it separately in order to raise a point about it (although I guess this is just personal preference).

I was also thinking that perhaps our main way of testing as of right now (running and comparing outputs against a test files) is more of an integration test rather than a unit test, and perhaps we could keep our current tests (but treat them as integration tests), and write more modular unit tests like the one in this PR for each block/check.

Just an idea I think we could consider for the reestructuring

assert main(filepaths) == -1


def test_complex_init(tmpdir):
Copy link
Collaborator

Choose a reason for hiding this comment

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

Could you add a unittest to silent valid cases?

I mean, imagine you have a odoo module to process a python file with a dict instead of a json (similar way __manifest__.py) so you will have python files without importing but not default one

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 think the best place to create python lints is pylint

It could be done using def open or using def visit_module in order to get all imported nodes for a particular module directory

Could we discuss it, please?

Yes, ideally Python code should be in pylint, I agree. I went with hooks since this PR was a joy to code haha, I feel the resulting code is clean and elegant, and the files and exclude options are helpful. I will look into moving it with the help of visit_module, I did not think of that before and it seems promising.


def main(argv: Union[List[str], None] = None) -> int:
parser = argparse.ArgumentParser()
parser.add_argument("filenames", nargs="*")
Copy link
Collaborator

Choose a reason for hiding this comment

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

Could you consider to add an extra parameter to change the exit_status in order to be able to enable it but without fails, please?

Similar to:
https://github.com/PyCQA/bandit/blob/d9fe642e01866e460454641bcd14b9de9d2b1478/bandit/cli/main.py#L363-L369

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 think the behavior for all these hooks should be standard. I understand we are the main users for it and changes like this can blow up our whole CI, but if we separate concerns, I think that is not a concern of this repo, but instead of the users (us).

There is already a "native" option for users to run hooks but make them optional, I think we could use that instead of changing the code.

https://stackoverflow.com/questions/59730765/is-it-possible-to-run-mypy-pre-commit-without-making-it-fail

def get_imported_files(dirname: str) -> Set[str]:
imported_files = set()
init_file = os.path.join(dirname, "__init__.py")
if os.path.exists(init_file):
Copy link
Collaborator

Choose a reason for hiding this comment

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

Prefer return early

Suggested change
if os.path.exists(init_file):
if not os.path.exists(init_file):
return imported_files

@moylop260
Copy link
Collaborator

moylop260 commented Nov 12, 2022

Do you think it could be good to create a new PR to https://github.com/pre-commit/pre-commit-hooks?

But using a generic way

@antonag32
Copy link
Contributor Author

Do you think it could be good to create a new PR to https://github.com/pre-commit/pre-commit-hooks?

But using a generic way

Maybe but I think importing stuff from __init__.py only applies to Odoo projects, at least based on my experience working with non-odoo Python projects, so perhaps it is not generic enough for that repo. Unless I am missing a generic way that this can be useful to most people working in non-odoo projects too.

@antonag32
Copy link
Contributor Author

antonag32 commented Nov 23, 2022

Replaced by: OCA/pylint-odoo#449

Pylint is a much better option, since imports and classdefs can be checked which means the check no longer depends on customs being followed.

@antonag32 antonag32 closed this Nov 23, 2022
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.

2 participants