-
-
Notifications
You must be signed in to change notification settings - Fork 12
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
Conversation
ac1b381
to
faeb201
Compare
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
faeb201
to
5bf9b76
Compare
I think the best place to create python lints is pylint It could be done using Could we discuss it, please? |
entry: oca-check-unused-python-file | ||
language: python | ||
types: [python] | ||
files: (controllers|models|report|wizard)\.py$|tests\/test_.*\.py$ |
There was a problem hiding this comment.
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/
There was a problem hiding this comment.
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): |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Use a setUp
to create tempdir and use a tearDown
to delete it
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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
.
There was a problem hiding this comment.
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): |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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 usingdef visit_module
in order to get all imported nodes for a particular module directoryCould 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="*") |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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.
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): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Prefer return early
if os.path.exists(init_file): | |
if not os.path.exists(init_file): | |
return imported_files |
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 |
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. |
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:
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 rightfiles
setting onpre-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:
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.