Pylint transform plugin and transforming imported module
Hi all, I'm trying to write a transform plugin to better work with a library that I maintain. For background my library extends a third-party library and uses some dynamic typing to add new features and wrappers on top of the third-party library. Because it uses dynamically created classes pylint complains about having missing members (E1101) and unexpected keyword arguments in the constructor (E1123). The class that is being dynamically generated only gets generated once when the module is imported and provides a proxy/wrapper around an existing class from the third-party library. The wrapper mirrors the member functions from the original class, as well as overriding and adding some new member functions. So the structure is something like: tpmodule.py # third-party library class Control: .... mymodule.py # my library import tpmodule def _make_wrapper(towrap): .... # dynamically create new class Control = _make_wrapper(tpmodule.Control) user.py # user code to run pylint on import mymodule ctrl = Control(...) ctrl.original_func(...) ctrl.new_func(...) My plan with the transform plugin is to use 'astroid.MANAGER.register_transform()' to detect the 'astroid.Module' calls and turn 'mymodule.py' into something more palatable for pylint; in particular to replace 'mymodule.Control' with an 'astroid.ClassDef' that has the correct initializer and member functions. Unfortunately, things are not working the way I would like and I'm still getting the missing member and bad constructor arguments errors, even though, as far as I can see, I am returning the appropriately modified module. However, there are many things that I don't understand: 1) Relating to the above example, pylint seems to be treating the 'ctrl' variable in 'user.py' 'ctrl' as being of type 'tpmodule.Control' so doesn't complain about things that are common to 'tpmodule.Control' and 'mymodule.Control'. But I cannot understand how pylint could possibly detect the relationship between 'tpmodule.Control' and 'mymodule.Control' without getting into the guts of the 'mymodule._make_wrapper()' function. 2) It is not clear to me how pylint treats imports from external libraries? In my example tpmodule.py and mymodule.py are not things that I want pylint to report on. But presumably it still needs to follow some of the chain of dependencies to work out the signature of what is exposed by the module so I understand why it would parse 'mymodule.py', but I don't understand why it would parse 'tpmodule.py'. In fact, another curious thing is that I created a cut down example to highlight the problem and while it has some of the same errors it also behaves slightly differently. In the cut-down example pylint DOESN'T actually parse 'tpmodule.py' so 'tpmodule.Control' doesn't trigger the 'register_transform()' the way it does in the real system. Even though this makes more sense to me than what is happening in the actual code, it is unfortunate because I wanted to dynamically construct the signature of 'tpmodule.Control' so was relying on pylint parsing 'tpmodule.py'. Note for clarification, in the real system 'mymodule.py' and 'tpmodule.py' are more complex and both use functions from their respective internal modules. 'tpmodule.py' is actually written in C++ and is using CFFI to generate the Python API, so maybe that can explain some difference in the behaviour to the pylint expert? Anyway, I'm sorry for the long winded questions. However, I'm hoping someone has someone insight or can point me to some docs or a specific example that solves a similar issue. I can also post a zip file of my cut down example if it is appropriate to do so. It consists of four short files ('mymodule.py', 'tpmodule.py', 'user.py', and 'plugin.py'). Regards, Dave To be clear I'm not using pylint on the library itself, instead I want pylint to work nicely with users of the library; although at some point I will try to configure pylint to help me with refactoring some of the code as well. My library extends an existing library with some new feature. and does some dynamic typing. I'm new to the group and
Hello David, It look like you're trying to create a brain for astroid. Here's a recent example for signal directly in astroid recently : https://github.com/PyCQA/astroid/pull/1172/files Hope this helps :) Le mar. 5 oct. 2021 à 14:26, David Rajaratnam <daver@gemarex.com.au> a écrit :
Hi all,
I'm trying to write a transform plugin to better work with a library that I maintain. For background my library extends a third-party library and uses some dynamic typing to add new features and wrappers on top of the third-party library. Because it uses dynamically created classes pylint complains about having missing members (E1101) and unexpected keyword arguments in the constructor (E1123).
The class that is being dynamically generated only gets generated once when the module is imported and provides a proxy/wrapper around an existing class from the third-party library. The wrapper mirrors the member functions from the original class, as well as overriding and adding some new member functions. So the structure is something like:
tpmodule.py
# third-party library
class Control: ....
mymodule.py
# my library
import tpmodule
def _make_wrapper(towrap): .... # dynamically create new class
Control = _make_wrapper(tpmodule.Control)
user.py
# user code to run pylint on
import mymodule
ctrl = Control(...) ctrl.original_func(...) ctrl.new_func(...)
My plan with the transform plugin is to use 'astroid.MANAGER.register_transform()' to detect the 'astroid.Module' calls and turn 'mymodule.py' into something more palatable for pylint; in particular to replace 'mymodule.Control' with an 'astroid.ClassDef' that has the correct initializer and member functions. Unfortunately, things are not working the way I would like and I'm still getting the missing member and bad constructor arguments errors, even though, as far as I can see, I am returning the appropriately modified module.
However, there are many things that I don't understand:
1) Relating to the above example, pylint seems to be treating the 'ctrl' variable in 'user.py' 'ctrl' as being of type 'tpmodule.Control' so doesn't complain about things that are common to 'tpmodule.Control' and 'mymodule.Control'. But I cannot understand how pylint could possibly detect the relationship between 'tpmodule.Control' and 'mymodule.Control' without getting into the guts of the 'mymodule._make_wrapper()' function.
2) It is not clear to me how pylint treats imports from external libraries? In my example tpmodule.py and mymodule.py are not things that I want pylint to report on. But presumably it still needs to follow some of the chain of dependencies to work out the signature of what is exposed by the module so I understand why it would parse 'mymodule.py', but I don't understand why it would parse 'tpmodule.py'.
In fact, another curious thing is that I created a cut down example to highlight the problem and while it has some of the same errors it also behaves slightly differently. In the cut-down example pylint DOESN'T actually parse 'tpmodule.py' so 'tpmodule.Control' doesn't trigger the 'register_transform()' the way it does in the real system. Even though this makes more sense to me than what is happening in the actual code, it is unfortunate because I wanted to dynamically construct the signature of 'tpmodule.Control' so was relying on pylint parsing 'tpmodule.py'.
Note for clarification, in the real system 'mymodule.py' and 'tpmodule.py' are more complex and both use functions from their respective internal modules. 'tpmodule.py' is actually written in C++ and is using CFFI to generate the Python API, so maybe that can explain some difference in the behaviour to the pylint expert?
Anyway, I'm sorry for the long winded questions. However, I'm hoping someone has someone insight or can point me to some docs or a specific example that solves a similar issue. I can also post a zip file of my cut down example if it is appropriate to do so. It consists of four short files ('mymodule.py', 'tpmodule.py', 'user.py', and 'plugin.py').
Regards,
Dave
To be clear I'm not using pylint on the library itself, instead I want pylint to work nicely with users of the library; although at some point I will try to configure pylint to help me with refactoring some of the code as well.
My library extends an existing library with some new feature. and does some dynamic typing.
I'm new to the group and _______________________________________________ code-quality mailing list -- code-quality@python.org To unsubscribe send an email to code-quality-leave@python.org https://mail.python.org/mailman3/lists/code-quality.python.org/ Member address: pierre.sassoulas@gmail.com
Hi Pierre, Thanks. I will look at this more closely and the register_module_extender(). Cheers, Dave On 5/10/21 11:56 pm, Pierre Sassoulas wrote:
Hello David,
It look like you're trying to create a brain for astroid. Here's a recent example for signal directly in astroid recently : https://github.com/PyCQA/astroid/pull/1172/files <https://github.com/PyCQA/astroid/pull/1172/files>
Hope this helps :)
Le mar. 5 oct. 2021 à 14:26, David Rajaratnam <daver@gemarex.com.au <mailto:daver@gemarex.com.au>> a écrit :
Hi all,
I'm trying to write a transform plugin to better work with a library that I maintain. For background my library extends a third-party library and uses some dynamic typing to add new features and wrappers on top of the third-party library. Because it uses dynamically created classes pylint complains about having missing members (E1101) and unexpected keyword arguments in the constructor (E1123).
The class that is being dynamically generated only gets generated once when the module is imported and provides a proxy/wrapper around an existing class from the third-party library. The wrapper mirrors the member functions from the original class, as well as overriding and adding some new member functions. So the structure is something like:
tpmodule.py
# third-party library
class Control: ....
mymodule.py
# my library
import tpmodule
def _make_wrapper(towrap): .... # dynamically create new class
Control = _make_wrapper(tpmodule.Control)
user.py
# user code to run pylint on
import mymodule
ctrl = Control(...) ctrl.original_func(...) ctrl.new_func(...)
My plan with the transform plugin is to use 'astroid.MANAGER.register_transform()' to detect the 'astroid.Module' calls and turn 'mymodule.py' into something more palatable for pylint; in particular to replace 'mymodule.Control' with an 'astroid.ClassDef' that has the correct initializer and member functions. Unfortunately, things are not working the way I would like and I'm still getting the missing member and bad constructor arguments errors, even though, as far as I can see, I am returning the appropriately modified module.
However, there are many things that I don't understand:
1) Relating to the above example, pylint seems to be treating the 'ctrl' variable in 'user.py' 'ctrl' as being of type 'tpmodule.Control' so doesn't complain about things that are common to 'tpmodule.Control' and 'mymodule.Control'. But I cannot understand how pylint could possibly detect the relationship between 'tpmodule.Control' and 'mymodule.Control' without getting into the guts of the 'mymodule._make_wrapper()' function.
2) It is not clear to me how pylint treats imports from external libraries? In my example tpmodule.py and mymodule.py are not things that I want pylint to report on. But presumably it still needs to follow some of the chain of dependencies to work out the signature of what is exposed by the module so I understand why it would parse 'mymodule.py', but I don't understand why it would parse 'tpmodule.py'.
In fact, another curious thing is that I created a cut down example to highlight the problem and while it has some of the same errors it also behaves slightly differently. In the cut-down example pylint DOESN'T actually parse 'tpmodule.py' so 'tpmodule.Control' doesn't trigger the 'register_transform()' the way it does in the real system. Even though this makes more sense to me than what is happening in the actual code, it is unfortunate because I wanted to dynamically construct the signature of 'tpmodule.Control' so was relying on pylint parsing 'tpmodule.py'.
Note for clarification, in the real system 'mymodule.py' and 'tpmodule.py' are more complex and both use functions from their respective internal modules. 'tpmodule.py' is actually written in C++ and is using CFFI to generate the Python API, so maybe that can explain some difference in the behaviour to the pylint expert?
Anyway, I'm sorry for the long winded questions. However, I'm hoping someone has someone insight or can point me to some docs or a specific example that solves a similar issue. I can also post a zip file of my cut down example if it is appropriate to do so. It consists of four short files ('mymodule.py', 'tpmodule.py', 'user.py', and 'plugin.py').
Regards,
Dave
To be clear I'm not using pylint on the library itself, instead I want pylint to work nicely with users of the library; although at some point I will try to configure pylint to help me with refactoring some of the code as well.
My library extends an existing library with some new feature. and does some dynamic typing.
I'm new to the group and
_______________________________________________ code-quality mailing list -- code-quality@python.org <mailto:code-quality@python.org> To unsubscribe send an email to code-quality-leave@python.org <mailto:code-quality-leave@python.org> https://mail.python.org/mailman3/lists/code-quality.python.org/ <https://mail.python.org/mailman3/lists/code-quality.python.org/> Member address: pierre.sassoulas@gmail.com <mailto:pierre.sassoulas@gmail.com>
_______________________________________________ code-quality mailing list -- code-quality@python.org To unsubscribe send an email to code-quality-leave@python.org https://mail.python.org/mailman3/lists/code-quality.python.org/ Member address: daver@gemarex.com.au
participants (2)
-
David Rajaratnam
-
Pierre Sassoulas