-
-
Notifications
You must be signed in to change notification settings - Fork 1.2k
Add a specifier for alerts that are linked to a library #9539
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
Comments
Hi @PeterHamfelt perhaps this could be relevant to your question -
>>> from importlib.metadata import version
>>> version("pandas")
'2.2.1'
>>> |
Thank you Marc ! I think we also need to specify how we handle the message declaration of the lib version in the checker (this is already done for python version we can build on it). Example of use for the current minversion: from pylint.checkers import BaseChecker
class AsyncChecker(BaseChecker):
msgs = {
"E1700": (
"Yield inside async function",
"yield-inside-async-function",
"Used when an `yield` or `yield from` statement is "
"found inside an async function.",
{"minversion": (3, 5)},
),
} ... But we must also declare what lib(s) is (are) affected. So possible data structure: from pylint.checkers import BaseChecker
class PandasNumpyChecker(BaseChecker):
msgs = {
"E0042": (
"Some pandas/numpy version specific message",
"pandas-numpy-message",
"Some pandas/numpy version specific message rational",
{"minversion": (3, 5), "libversion": {
"pandas" : {"minversion": "1.0.1", "maxversion": "2.0.1"}, # version could be tuple
"numpy": {"minversion": "1.0.1}, #, but that's not what importlib.metadata.version returns
}},
),
}
from pylint.checkers import BaseChecker
class DjangoChecker(BaseChecker):
# ....
msgs = {
#...
"libversion": {
"django" : {"onmissing": "fatal"}, # if None nothing is done, if "fatal" a fatal error message is raised.
}
} Let me know what you think about this spec proposal ;) |
Looks good @Pierre-Sassoulas 👌 |
Hi @mbyrnepr2, @Pierre-Sassoulas , I apologize for the delayed response. I've reviewed the suggested setup for specifying library versions and I'm genuinely impressed. The structure you proposed: "libversion": { This approach, especially considering versions as a range (with minimum and maximum bounds), seems both robust and flexible. I believe this could be particularly advantageous for pylint-ml. Given that each checker might depend on specific library versions, and these dependencies are subject to evolve over time, integrating this implementation would significantly enhance the analyzer's adaptability and accuracy. I'm looking forward to discussing this further and exploring how we can incorporate this feature. |
How do we handle non |
Good point, we must use string in the data structure not tuple and then handle the complexity internally. Other versioning schemes I know are:
We can't use use alphabetical ordering on the string because we need to detect that |
I think we should utilize something like https://github.com/pypa/packaging. |
On the one hand, yeah definitely. On the other hand I'd really hate to add a new dependency. Maybe we can live with supporting only semver or semver adjacent. |
Maybe this doesn't address your concern Pierre but maybe it could be added as an extra dependency since users of Pylint itself wouldn't need it; whereas the plug-in dependency on Pylint would be pylint[packaging]. |
If pylint does not support lib-version dependency, what would be the recommended approach to handle this locally for pylint plugins? |
If we add it as an extra, we'll still have to deal with all the issue associated with a new dependency (for dill we had to track an issue for months, then Daniel had to create our own version of dill so we can release pylint in time for python 3.12). My main concern is the maintenance cost and the bloat. The complexity seems high even if we only take what we need and vendor it in pylint : https://github.com/pypa/packaging/blob/32deafe8668a2130a3366b98154914d188f3718e/src/packaging/version.py#L117-L146 / https://github.com/pypa/packaging/blob/32deafe8668a2130a3366b98154914d188f3718e/src/packaging/version.py#L200-L202 What do you mean by "lib-version dependency" @PeterHamfelt ? |
I understand your concern @Pierre-Sassoulas. Since such checks are not currently supported by pylint, what would be the recommended generic approach for incorporating this type of functionality into pylint plugins? |
That would be semver, It's the most common versioning scheme, if we do the feature we're going to support this whatever we choose :) Let's vote on what to do. Please let me know if you think of another solution, I'll add it to the vote :
|
"Add pypa/packaging as a dependency and support everything 🚀" |
@jacobtylerwalls do you have an opinion on the subject ? |
I'm for adding pypa/packaging. It looks stable, small footprint, and already supports python 3.13. dill is a much more sensitive subject area. I don't think we're taking on a big risk to add it. |
Sorry for not stating that earlier, probably due to lack of sleep, but two reasons why I'm reluctant to add a dependency if it's possible to avoid it, are: Dependencies are vastly more disruptive in pylint than in any other projects. pylint must be installed alongside a project's own dependencies to properly analyses a project. So it means pylint dependencies can conflict with the project's (i.e. we want Also, one first step to increase the security of a package is to set the dependencies to exact versions (so that a new corrupted version of So 😄 / ❤️ would be my personal choice.. anyway if the votes stays what it is with the new info, I'll disagree and commit :) (*) in this case pytorch was requiring a specific old version, but this could happens more often if more maintainers choose to set dependencies to specific versions because of point 2 |
I suppose we could go for😄 or👀 initially and if future plugins need the added functionality of the dependency, it could be considered then (we could change our mind)? |
Can we delegate all of this decision making to the plugin? |
Nice 🧠 ! Yes, permitting to inject a comparison function on the plugin side and provide semver by default in pylint "core" would be a really nice design. from pylint.checkers import BaseChecker
def _esoteric_lib_comparator(actual_version: str, min_version: str, max_version)-> bool:
return min_version < actual_version < max_version
class PandasSomeEsotericLibChecker(BaseChecker):
msgs = {
"E0042": (
"Some pandas/numpy version specific message",
"pandas-numpy-message",
"Some pandas/numpy version specific message rational",
{"minversion": (3, 5), "libversion": {
"pandas" : {
"minversion": "1.0.1",
"maxversion": "2.0.1",
"onmissing": "fatal",
"versioncomparator": None
},
"someesotericlib": {
"minversion": "duck",
"maxversion": "spoon",
"versioncomparator": _esoteric_lib_comparator
},
}},
),
} not sure about "versioncomparator", sounds more like a check to see if the installed lib is in range. so "versionvalidator" ? |
This looks good to me 😃🐼 |
Do we have a decision? |
Can |
I'm not sure what you mean by that, could you upgrade the |
Just make: def _esoteric_lib_comparator() -> bool: ... If we need to pass the version we need to have a way to get the version of a package for which we might do |
Ha, right, I understand. Are there a lot of other ways to recover the version ? Maybe we could provide another injection point for that if that's the case: import antigravity
from pylint.checkers import BaseChecker
def _esoteric_lib_version_comparator(actual_version: str, min_version: str, max_version)-> bool:
return min_version < actual_version < max_version
def _esoteric_lib_version_recoverer() -> str:
return antigravity.push("elephant")
class PandasSomeEsotericLibChecker(BaseChecker):
#...
"someesotericlib": {
"minversion": "duck",
"maxversion": "spoon",
"versioncomparator": _esoteric_lib_version_comparator,
"versionrecoverer": _esoteric_lib_version_recoverer,
}, That way you don't have to redefine the version recovering if you want to handle something else than semver. We'd have to implement the default way to do it in pylint so |
I think this is too paranoid. :D |
I mean, the docs are full of warnings and caveats. I would personally prefer delegating all of this to plugins by making the API have no arguments at all. We can always add an argument later if plugins can't get the data they need themselves. Removing arguments later is much more of a hassle. |
Sorry, I'm not grokking the caveats. Do you have one in that doc you might point me to? |
"Specifically, it works with distributions with discoverable dist-info or egg-info directories, and metadata defined by the Core metadata specifications." These are not necessarily equivalent to or correspond 1:1 with the top-level import package names that can be imported inside Python code." I'm mostly worried about an influx of issues about "my obscure poorly packaged python library doesn't work with pylint" etc. I don't really see why we need to pass so much to the plugins why they could call |
Fair, but I think we can document that this interface is only for checkers to declare dependencies on pip-installable, importlib.metadata.version()'able packages.
This was my first thought also. @Pierre-Sassoulas @PeterHamfelt any thoughts on whether we truly need anything in core pylint for this? Is it because we want pylint to throw an error when you load a plugin requiring a dependency you don't have? That might be nice to handle in core. |
I think we should provide a way for plugin maintainer to not have to re-implement the importlib.metadata's way of recovering the version. It's simpler for plugin creation and will reduce the duplication of code. Having a best effort default version recoverer in pylint core is a must have imo. If we really want to permit to recover a version another custom way, it should not complexify the general case's design and force plugin maintainer to consider version comparison if they only want to change version recovery. (i.e. two dependency injections "versioncomparator" with default semver / "versionrecoverer" with default importlib.metadata ; not making an API like "isvalidversion" that must handle both). That being said, I don't know how many pylint plugins will be made for packages that do not even bother with proper metadata. My guess is that bad packaging is going to affect small packages, and small packages do not have specific pylint plugins. The plugins I know target pytest, numpy, pandas, tensorflow... huge (normalized) libs. So probably very few problematic use cases ? And we can implements |
Oh yeah we don't want the plugin maintainer reimplementing nothing. I just had a bigger YAGNI question about this whole thing. Why can't the plugin just do: self.pandas_version = version("pandas")
def my_check():
if self.pandas_version < whatever:
return # or warnings.warn, or whatever None of that requires changes in core pylint. Why do we need changes in core? |
Well, it would mean that every plugin needs to re-implement that check in each of their plugin, plus proper user interface to warn that the check is not available in this version of a lib or that the lib is not installed. We're providing a similar |
I guess that's what I'm getting at. If this is the goal, let's start discussing the specifics of that and make sure we have a volunteer with a draft PR. If we don't have one, then I'd say let's consider wontfixing. Just my 2¢ |
Sorry for the delayed response. I just read through the thread and thanks for discussing the issue!
Exactly, this is what I was asking for. Will it be up to each pylint-plugin to re-implement this solution or can we utilize a standard implemented in core? |
Hi, I wanted to provide a quick update on the current solution I've implemented for checking the required_version of a given library. I've created a LibraryBaseChecker class that tracks both import and importfrom statements. This allows checkers that inherit from LibraryBaseChecker to verify whether the specified library is imported and ensure the version meets the required criteria. Here’s a simplified version of the implementation:
` This solution handles both the detection of library imports and version validation. Let me know if you have any feedback or suggestions for further improvements. |
Feel free to open a PR in pylint core so we can iterate on it. I think it should be integrated in the default checker in core. There's a lot of specification already done above. I planned to implement it at some point but there's just too much on my plate right now. |
Okay, sound good @Pierre-Sassoulas! |
Current problem
I'm trying to find a solution to specify an alert which is specific to a given library and version of that library.
An example is the pylint-ml plugin which has checkers associated with the Pandas library.
pylint-dev/pylint-ml#23
Desired solution
When creating a checker, I would like to be able to add
The question I have is how can we check which lib version the user is using or does this need to be configured manually?
I am open to suggestions :)
Additional context
No response
The text was updated successfully, but these errors were encountered: