-
-
Notifications
You must be signed in to change notification settings - Fork 168
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] New unused-module check #449
Conversation
Fixes part of Vauxoo#177. Empty files should be checked with a pre-commit hook instead. |
@moylop260 Could you review please |
There hasn't been any activity on this pull request in the past 4 months, so it has been marked as stale and it will be closed automatically if no further activity occurs in the next 30 days. |
Should this be revived? @moylop260 @luisg123v |
Please check |
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.
Please add comments to the code, code is not trivial and there's not a single comment
@@ -0,0 +1,10 @@ | |||
{ | |||
'name': 'Test Import Messages', |
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.
Are you sure you need a new module?
CAn't you reuse some of the already existing broken modules?
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 it is cleaner to have test files for each message, or at least have them groupd. pylint does some variation of this and I find it easier to work with, since you don't have to look around different broken modules to find the offending code.
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 differently, creating a new module will trigger errors in other checkers. Actually, you had to modify unrelated expected errors for this to work.
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, as long as the test sources are shared between checkers adding new checkers/modifying them runs the risk of breaking the whole thing. This is unrelated to the issue itself so I guess I will just use the errors that naturally pop up in existing broken modules instead of creating new files (brings no real improvement as of now)
@@ -1,3 +1,4 @@ | |||
from . import odoo_addons | |||
from . import vim_comment | |||
from . import custom_logging | |||
from . import import_checker |
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 a new file? Shouldn't you implement new lints into the existing file for Odoo addons?
@moylop260 I don't have enough context to know if this is correct.
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 odoo_addons is getting too big and grouping lints inside different checkers could help organization.
if import_stmt.modname == "odoo": | ||
if any("models" in name for name in import_stmt.names): | ||
results.add("models") | ||
elif import_stmt.modname == "odoo.models": |
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.
Is it not the same than?
pylint-odoo/src/pylint_odoo/checkers/odoo_addons.py
Lines 1440 to 1442 in dd98797
if "odoo" in package_names: | |
class_base_name = class_base.as_string().split(".")[-1] | |
if class_base_name in ["Model", "AbstractModel", "TransientModel"]: |
@lru_cache() | ||
def _get_odoo_import_orm_names(self, module: Module) -> Set[str]: | ||
results = set() | ||
for import_stmt in filter(lambda node: isinstance(node, ImportFrom), module.body): |
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 you are redundant the visit here
I mean, you have a classdef then get the parent then filter all the module.body the ImportForm nodes
But then you have visit_importfrom
It should contains the same than this one
Is not?
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 think I can skip this and just use visit_importfrom
, will look into it
odoo_imports = self._get_odoo_import_orm_names(node.parent) | ||
if "models" in odoo_imports: | ||
not_orm = not any( | ||
(basename.startswith("models.") or basename in odoo_imports) for basename in node.basenames |
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.
Think this lint could be good in the pycqa/pylint project
Could you remove the models
things related, please?
In fact, The original issue is not related with models
but controllers
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 this check should be more standard than only models
I mean, if the folder has a __init__.py
so get all the .py
files in the same path vs the visit_importfrom
Without visiting a particular classdef
Maybe pycqa/pylint could adopt it
The known valid cases are:
migrations/
where doesn't have a __init__.py
file
__init__.py
must be auto-discarded
__manifest__.py / __openerp__.py
use an extra option to discard names
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.
Sorry, what would be the generic check exactly? AFAIK outside of Odoo it is normal to have empty init.py files, so I don't think it would be helpful.
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 did the models thing to extend it and make sure that both controllers and models are imported in their __init__.py
so they make an effect.
If I add a utility class inside the models folder, it is not required to import it in the __init__.py
, I think that is why I checked for class defs.
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 I understand now, do you mean checking that all .py files are imported somewhere (referenced)? In the case of Odoo I think some need to be imported in __init__.py
to make an effect but I can now see how it may be generic enough.
I will look at existing pylint messages to see if I can find something similar.
@@ -0,0 +1,6 @@ | |||
from odoo.models import BaseModel |
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 test if the disable-check-comment works, please?
@moylop260 I have refactored the code to make it more generic and applicable to all files (except those that we agreed to exclude). I also opened an issue on pylint so lets see where that goes. The pipeline will be all red because I had some trouble running tox, I plan to fix that and tidy up the MR tomorrow. |
5aa8f7c
to
57f80d7
Compare
I had to make a small refactoring to prevent this and future messages from destroying the whole test suite (the new check was triggering on existing broken modules and the new source code was triggering other checkers). As a bonus point messages that don't need a whole module can now use single files (like pylint does). As for the checker, it is mostly complete, just need to take into account |
e33765b
to
ef601e1
Compare
I think everything is ready.
|
Could you review it, please? |
src/pylint_odoo/checkers/__init__.py
Outdated
from . import odoo_addons | ||
from . import vim_comment | ||
from . import custom_logging | ||
from . import custom_logging, import_checker, odoo_addons, vim_comment |
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 now in one line? I think this is not expected
# C->convention R->refactor W->warning E->error F->fatal | ||
"E8401": ( | ||
"This python module is not imported and has no effect", | ||
"unused-module", |
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.
IMHO this lint name is not specific enough. What kind of module? Take into account there are Odoo modules and Python modules
I'd say Odoo models instead, e.g. odoo-model-not-imported
.
What do you think?
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.
Initially it was for odoo models only but according to the previous review by Moy I made it generic. By Python module I mean and think I am following the official specification which as far as I understand is any .py
file.
As of now this basically detects unused python files
|
||
@staticmethod | ||
def get_module_from_node(node, max_attempts: int = 15) -> Union[None, Module]: | ||
"""Get the module a node belongs to. |
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.
Fix semantic, this is not clear
Did you mean "Return whether the module "a" belongs to the provided node"?
I'm not sure
"""Get the module a node belongs to. | ||
|
||
:param node: Node that belongs to the module to be extracted. | ||
:param int max_attempts: Amount of attempts that will be made to obtain the module. Nodes that are |
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.
amount -> number
|
||
:param node: Node that belongs to the module to be extracted. | ||
:param int max_attempts: Amount of attempts that will be made to obtain the module. Nodes that are | ||
nested deeper than max_attempts won't be found. |
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.
indent
module = ImportChecker.get_module_from_node(node) | ||
if not module: | ||
return | ||
if "tests" in module.name: |
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.
Join with the above conditional
return | ||
|
||
if isinstance(node, ImportFrom): | ||
level = 0 if not node.level else node.level |
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 positive conditions instead of negative ones
It's more readable:
if condition:
x = 1
else:
x = 2
than:
if not condition:
x = 2
else:
x = 1
Because else would be a double negation
elif isinstance(node, Import): | ||
level = 1 | ||
else: | ||
raise ValueError("Node must be either ImportFrom or Import") |
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 don't think you should raise exceptions inside lints.
|
||
slice_index += 1 | ||
|
||
if slice_index != 0: |
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.
if slice_index != 0: | |
if slice_index: |
if slice_index != 0: | ||
root_module = module_name[:-slice_index] | ||
else: | ||
root_module = module_name |
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 inline conditional
another excluded directory: doc |
2cac199
to
0747f2b
Compare
@luisg123v I have made the requested changes, the only thing left is my question about the code on docstrings. |
There hasn't been any activity on this pull request in the past 4 months, so it has been marked as stale and it will be closed automatically if no further activity occurs in the next 30 days. |
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.
This needs local rebase.
Previously faulty code which generated messages was distributed throught multiple repositories. Tests based themselves on the total amount of expected messages contained within the repositories. This is not sustainable because adding new checks oftentime triggers unintended messages on all repos, breaking tests and costing developer time. This commit freezes test repos to keep backwards compatibility, however all new checks must implement their own separate test sources, this will speed up development and help organization.
This new check looks for python files (modules) which are not referenced anywhere in the codebase, making them useless. Closes Vauxoo#177.
I have rebased it. I have also realized I have become a less destructive person since I am double thinking the frozen tests thing. I still think it makes sense to keep test files separately, since that reduces lots of headaches and means adding new messages does not blow up on old test files. However I wonder if there is any cleaner way? Maybe it is better to just keep rolling the old way even if it feels "wrong", it seems like the whole test suite was built around the old way. |
There hasn't been any activity on this pull request in the past 4 months, so it has been marked as stale and it will be closed automatically if no further activity occurs in the next 30 days. |
This new check finds all files that have a class which inherits from one of Odoo's models classes and ensures they are imported.
This is required since classes that interact with the ORM (create models, or inherit them) MUST be imported in the corresponding init.py file, otherwise they won't have an effect.