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

Address all pylint errors in the current codebase #58

Merged
merged 55 commits into from
Jun 10, 2021

Conversation

lbianchi-lbl
Copy link
Contributor

@lbianchi-lbl lbianchi-lbl commented Nov 11, 2020

Summary/Motivation

Get rid of all pylint errors to obtain a "clean slate" so that pylint can be activated as a required check for future PRs.

Strategy

This PR implement changes aimed to address everything that pylint reports as an error in different ways, depending if the reported error is a false positive, i.e. a statement that pylint considers a cause of runtime error, but that doesn't actually cause errors at runtime, or a true positive, i.e. a statement that would (or does) result in a runtime error if that portion of the code is run:

  • For false positives, the fixes only affect pylint and do not change the runtime behavior of the code
    • For one-off errors, add a pylint directive instructing pylint to ignore a particular error for that statement or scope
    • For systematic errors, i.e. a construct or implementation that's used in multiple places in the codebase, develop general solutions that address all related error at the same time. Typically this is done by either:
      • Adding a global directive affecting the entire codebase as a pylint configuration option
      • Creating a pylint transform plugin that "explains" the offending construct to pylint so that it matches the runtime behavior
  • For true positives, more care is needed as the fixes will change the runtime behavior of the code
    • If the cause of the error is clear, the intended behavior of the code is unambiguous, and the fixed implementation has no or very little chance of affecting the rest of the code, then I'll try to fix the issue myself, asking reviewers to confirm that the fix is valid
    • If these conditions do not apply, I'll refrain from making assumptions and ask the original author(s) to review the affected statement and comment or suggest a fix
    • For intermediate cases, I'll suggest a fix in a code comment, but refrain from actually implementing that fix until it has been reviewed
    • For all cases, I've marked the comment with PYLINT-TODO so that it's easy to find these fixes within the codebase

Changes proposed in this PR

  • Create .pylint top-level directory to store all pylint-related code and configuration
  • Move pylint configuration to pylintrc file
  • Add pylint transform plugins to address systematic false-positive errors specific to IDAES and/or Pyomo
  • Add custom pylint reporter to display a real-time log of errors in the CI interface and simultaneously save a machine-readable (JSON) dump of the error messages for further
  • Multiple unrelated changes throughout the codebase addressing true-positive errors detected by pylint

Reviewing

  • Changes that are already implemented in the code (i.e. anything that can be reviewed from the usual GitHub "Files changed" interface): please feel free to review as normal
  • Changes that are not implemented in the code: UPDATE 2021-05-21 All changes, including adding directives for errors still needing to be addressed, have been made in the code; see Address all pylint errors in the current codebase #58 (comment) for details
    • Remaining errors are listed in the checkist below. There is a link to each affected file in the header, and links pointing to the specific line where the error was detected (using the GitHub /blame/ URL endpoint to display the edit history of the affected line)
    • I'll be pinging the author(s) of the affected code (as indicated by git blame and/or previous conversations) both by requesting a PR review and by @-mentioning them pointing to specific lines; of course, everyone is more than welcome to chime in and clarify
    • The same applies to errors with an unchecked checkbox where I haven't yet mentioned anyone (either for lack of a clear author, because I forgot, etc)
    • In general, I expect that a number of these errors to be in parts of the code that are rarely or not used anymore (which is why they haven't been fixed until now); if this is the case, let me know if it makes sense for me to try to address the pylint errors at all or they can be skipped (because e.g. the files will be removed or deprecated soon)
  • pylint-specific functionality: as mentioned above, this only affects pylint and not the IDAES code itself, but feel free to ask questions about the intended behavior, implementation, impact on existing CI workflows, etc

Errors left to fix (list compiled on 2021-05-07, redundant as of 2021-05-21)

Click to display the original checklist

idaes.core.reaction_base

  • At line 536, in ReactionBlockDataBase.__getattr__: (link) (E1305) Too many arguments for format string (too-many-format-args)
  • @andrewlee94: the error comes from the format string having two "slots" but three arguments being used: .format(self.name, attr, attr). Would it be OK to change that to .format(self.name, attr)?

idaes.core.util.dyn_utils

  • At line 614, in copy_non_time_indexed_values: (link) (E0602) Undefined variable 'varname' (undefined-variable)
  • @Robbybp what should be used here instead of the non-existing varname?

idaes.core.util.unit_costing

  • At line 411, in pressure_changer_costing: (link) (E1305) Too many arguments for format string (too-many-format-args)
  • At line 438, in pressure_changer_costing.base_pump_rule: (link) (E1305) Too many arguments for format string (too-many-format-args)
  • At line 506, in pressure_changer_costing: (link) (E1305) Too many arguments for format string (too-many-format-args)
  • At line 574, in pressure_changer_costing: (link) (E1305) Too many arguments for format string (too-many-format-args)
  • At line 661, in pressure_changer_costing: (link) (E1305) Too many arguments for format string (too-many-format-args)
  • At line 858, in platforms_ladders.CPL_rule: (link) (E1305) Too many arguments for format string (too-many-format-args)
  • At line 865, in platforms_ladders: (link) (E1305) Too many arguments for format string (too-many-format-args)
  • At line 1005, in fired_heater_costing.CB_rule: (link) (E1305) Too many arguments for format string (too-many-format-args)
  • @MAZamarripa , all of these look similar (and similarly easy to fix). Could you take a look and let me know what the correct format args are supposed to be?

idaes.core.util.convergence.convergence_base

  • At line 652, in Stats.to_dmf: (link) (E0602) Undefined variable 'stats' (undefined-variable)
  • @eslickj the variable stats doesn't exist, but this is a method of the Stats class. Should stats be self instead?

idaes.core.util.convergence.mpi_utils

  • At line 137, in ParallelTaskManager.allgather_global_data: (link) (E0602) Undefined variable 'global_data_list_of_lists' (undefined-variable)

idaes.commands.base

  • At line 108: (link) (E1120) No value for argument 'verbose' in function call (no-value-for-parameter)
  • At line 108: (link) (E1120) No value for argument 'quiet' in function call (no-value-for-parameter)
  • @eslickj is this function still used here? if so, what arguments should be provided?

idaes.generic_models.unit_models.pressure_changer

  • At line 620, in PressureChangerData.model_check: (link) (E1305) Too many arguments for format string (too-many-format-args)

idaes.power_generation.flowsheets.supercritical_steam_cycle.supercritical_steam_cycle

  • At line 230, in create_model.mixer_pressure_constraint: (link) (E0102) function already defined line 182 (function-redefined)
  • At line 275, in create_model.mixer_pressure_constraint: (link) (E0102) function already defined line 182 (function-redefined)
  • At line 326, in create_model.mixer_pressure_constraint: (link) (E0102) function already defined line 182 (function-redefined)
  • @eslickj @andrewlee94: referring to our previous discussion about this in this PR, I'd do one of the following:
    • A Add a pylint directive so that this (false-positive) error is ignored (my choice all else being equal)
    • B Change the names of the functions so that they're unique (I'd need your input on what new names to use)

idaes.surrogate.main

  • At line 432, in Pysmo_rbf.handle_results: (link) (E1101) Instance of 'RadialBasisFunctions' has no 'rbf_generate_expression' member (no-member)
  • At line 484, in Pysmo_kriging.handle_results: (link) (E1101) Instance of 'KrigingModel' has no 'kriging_generate_expression' member (no-member)
  • @mengleCMU both of these errors are due non-existing methods, but the classes have methods with similar names that do exist: can you check if my guesses/assumptions in the comments above the affected line makes sense?

idaes.surrogate.ripe

  • At line 32: (link) (E0603) Undefined variable name 'massact' in __all__ (undefined-all-variable)

idaes.surrogate.ripe.write

  • At line 19, in writeripe: (link) (E0401) Unable to import 'ripe' (import-error)
  • At line 73, in writemodel: (link) (E0401) Unable to import 'ripe' (import-error)
  • At line 74, in writemodel: (link) (E0401) Unable to import 'alamopy' (import-error)
  • At line 294, in writeminlp: (link) (E0401) Unable to import 'ripe' (import-error)
  • @mengleCMU these errors are fixable, but the file starts with a comment saying that it is no longer used. Can you clarify what is the status of the file, and whether it would make sense for me to try and address the pylint errors or not?

idaes.surrogate.helmet.Helmet

  • At line 243, in viewMultResults: (link) (E1101) Module 'idaes.surrogate.helmet.Plotting' has no 'multSSECombo' member (no-member)

idaes.surrogate.helmet.GAMSDataWrite

  • At line 214, in SNDdt: (link) (E1306) Not enough arguments for format string (too-few-format-args)
  • At line 215, in SNDdt: (link) (E1306) Not enough arguments for format string (too-few-format-args)
  • At line 216, in SNDdt: (link) (E1306) Not enough arguments for format string (too-few-format-args)

idaes.surrogate.alamopy.doalamo

  • At line 462, in getTrainingData: (link) (E1102) almerror is not callable (not-callable)
  • At line 464, in getTrainingData: (link) (E1102) almerror is not callable (not-callable)
  • At line 635, in addBasisConstraints: (link) (E0602) Undefined variable 'group_constraints' (undefined-variable)
  • At line 637, in addBasisConstraints: (link) (E0602) Undefined variable 'basis_constraints' (undefined-variable)
  • At line 801, in parseKwargs: (link) (E1102) almerror is not callable (not-callable)
  • At line 996, in readTraceFile: (link) (E0602) Undefined variable 'writethis' (undefined-variable)
  • At line 1213, in _construct_mock: (link) (E0602) Undefined variable 'debug' (undefined-variable)

idaes.surrogate.alamopy.allcard

  • At line 22, in allcard: (link) (E0401) Unable to import 'alamopy.writethis' (import-error)

Original Summary/Motivation (superseded and only partially relevant as of 2021-05-07)

Transferred here from IDAES/idaes-dev#1199

This PR implements changes aimed at getting rid of false-positives errors, i.e. portions of the code that do not cause runtime errors, but are erroneously flagged as such by pylint because of limitations in pylint's static analysis.

In particular, it introduces a pylint transform plugin that makes pylint aware of ProcessBlock subclasses that are dynamically generated at runtime. This eliminates the pylint errors described in IDAES/idaes-dev#1159 without requiring any changes to the code.

This is part of the efforts towards IDAES/idaes-dev#1149. Specifically, this PR contains only changes that affect pylint exclusively, and thus do not alter the runtime behavior of the code in any way, i.e.:

  • pylint configuration
  • pylint commands in the CI configuration
  • pylint directives expressed as comments throughout the codebase

The idea is to keep those sets of changes that do affect the code itself (and that therefore could alter the runtime behavior) in dedicated PRs, to streamline both the fixes and the review process. In other words, reviews for this PR can concentrate entirely on the pylint analysis, without having to take into account compatibility, introducing regressions, or other code-specific considerations.

In addition, these changes aggregate most of the configuration into the pylint RC file located at .pylint/pylintrc, allowing to share the same settings among different use cases in addition to CI checks, e.g. direct invocation, and integration with IDEs/editors supporting static analysis with pylint.

Changes proposed in this PR

  • Add pylint transform plugin addressing ProcessBlock subclasses false positives
  • Restructure pylint setup so that the same configuration can be reused in multiple contexts such as CI and IDE/editor code check

Legal Acknowledgement

By contributing to this software project, I agree to the following terms and conditions for my contribution:

  1. I agree my contributions are submitted under the license terms described in the LICENSE.txt file at the top level of this directory.
  2. I represent I am authorized to make the contributions and grant the license. If my employer has rights to intellectual property that includes these contributions, I represent that I have received permission to make contributions and grant the required license on behalf of that employer.

@lbianchi-lbl lbianchi-lbl self-assigned this Nov 11, 2020
@lbianchi-lbl lbianchi-lbl marked this pull request as draft November 11, 2020 20:47
@lbianchi-lbl lbianchi-lbl force-pushed the pylint-false-positives branch from 9bec1e7 to d261c8a Compare November 19, 2020 02:10
@lbianchi-lbl lbianchi-lbl linked an issue Nov 19, 2020 that may be closed by this pull request
@ksbeattie ksbeattie added the Priority:Normal Normal Priority Issue or PR label Jan 21, 2021
@ksbeattie ksbeattie added Priority:High High Priority Issue or PR and removed Priority:Normal Normal Priority Issue or PR labels Mar 4, 2021
@lbianchi-lbl lbianchi-lbl force-pushed the pylint-false-positives branch from d261c8a to 1840c47 Compare March 25, 2021 17:49
Copy link
Member

@andrewlee94 andrewlee94 left a comment

Choose a reason for hiding this comment

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

A few comments on things that I am responsible for or understand. For the most part, these look good, but I would tend towards fixing our issues rather than turning off pylint features.

@@ -268,6 +268,9 @@ def report(self, time_point=0, dof=False, ostream=None, prefix=""):
f"Activated Blocks: {nb}")

if performance is not None:
# PYLINT-WHY: pylint has no way of knowing that performance is supposed to be dict-like
# pylint: disable=unsubscriptable-object
# PYLINT-TODO: alternatively, have the function return an empty dict and test with `if performance:`
Copy link
Member

Choose a reason for hiding this comment

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

I do not see a problem with this suggestion (although I cannot guarantee it won't break something somewhere else). I don't think there was a particular reason for having get_performance_contents return None by default.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

A quick check showed at least two unit tests in test_process_base() that would need to be updated. Apart from the (however minor) chance of breaking the existing API, I'm not sure I agree with pylint that this is an error: personally, for this and similar cases, I don't think there's anything particularly wrong with returning None as a sentinel value.

Seeing that all errors of the same type are related to the use of the same _get_performance_contents() in derived classes, I would propose to add this specific error (E1128: Assigning result of a function call, where the function returns None (assignment-from-none)) to the list of globally ignored checks, and leave the similar but semantically quite different E1111: Assigning result of a function call, where the function has no return (assignment-from-no-return) active, as IMO it covers a case that is much more likely to be accidental ("forgetting" to return a value where the client expects some, versus explicitly using return None).

**kwargs,
default=default,
initialize=initialize)
return self.state_block_class( # pylint: disable=not-callable
Copy link
Member

Choose a reason for hiding this comment

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

For this case, I don't see any reason why we could not make state_block_class a method rather than a property - that way pylint should be able to tell that it is callable.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

One consequence that this change would have is that clients of state_block_class would have to use it as a callable (state_block_class()), i.e. breaking the current API. This would not only require changing all call sites (about 20 from a quick search), but also having to use the (IMO slightly awkward) double-parens syntax to call the class itself (e.g. state_block = my_block.state_block_class()(**state_block_params)).

Taken these into account, and considering that I don't think there's anything wrong with the current solution of using a callable through a property (a reasonable strategy to effectively "build" an instance-specific method at runtime), I can see the argument for leaving the pylint directive and avoid changing the code.

idaes/core/property_meta.py Outdated Show resolved Hide resolved
idaes/core/reaction_base.py Show resolved Hide resolved
__all__ = [
'ReactionBlock', # pylint: disable=undefined-all-variable
'ReactionParameterBlock' # pylint: disable=undefined-all-variable
]
Copy link
Member

Choose a reason for hiding this comment

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

@eslickj Do we even actually use this __all__ attribute?

@@ -224,6 +224,10 @@ def mixer_pressure_constraint(b, t):
@m.fs.hotwell.Constraint(m.fs.time)
def mixer_pressure_constraint(b, t):
return b.condensate_state[t].pressure == b.mixed_state[t].pressure
# PYLINT-TODO check if the "function-redefined" pylint error can be addressed
Copy link
Member

Choose a reason for hiding this comment

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

This is a tricky one. Pylint is correctly identifying that the methods are being redefined, although that does not affect code functionality as they are being used in different namespaces (which pylint cannot easily see). This could easily be fixed by changing the name of the method (e.g. appending a prefix or suffix for the unit in question), but we would need to make sure that other parts of the code are not looking for these constraints by name.

Alternatively, using call syntax would work fine, and avoid any issues with changing the name of the constraint. The rules are sufficiently different to preclude writing a common method for all, but each constraint could define a new method with a unique name and not affect the code.

Copy link
Member

Choose a reason for hiding this comment

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

Short answer - I suggest refactoring the code, which should not be hard.

Copy link
Contributor Author

@lbianchi-lbl lbianchi-lbl Mar 27, 2021

Choose a reason for hiding this comment

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

Thanks for the explanation, it was useful for me to understand the context a bit better. On second thought, my personal preference would be to add the pylint directive (leaving the code as it is) rather than making changes to the code:

  • I initially thought that the functionality (i.e. the inner function's body) was identical in all cases, but as you point out, this is not the case
  • This particular portion of the code doesn't seem to be covered by unit tests (might be by component/integration tests)
  • I peeked at the source code for Constraint in pyomo.core.base.constraint and, unless the implementation used in this part of the IDAES code is significantly simpler, I think I've seen enough dark magic metaprogramming to make me hesitant to jump in and make the changes without more research or amulets tests
  • In general, I would argue that the type of error that pylint is flagging here does not apply in this particular situation, as the decorator is applied directly to the function definition; therefore, I'd lean more toward considering this a false positive

Copy link
Member

Choose a reason for hiding this comment

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

I agree that it is a false positive, but I could also see it confusing someone reading the code as well. For me, I am somewhat hesitant to add pylint directive unless absolutely necessary, in case this ends up masking real problems in the future.

That said, this is not something I would ask you to fix - if we were going to change the model code I would ask the powerplant team to do it.

idaes/power_generation/unit_models/helm/condenser_ntu.py Outdated Show resolved Hide resolved
idaes/power_generation/unit_models/helm/valve_steam.py Outdated Show resolved Hide resolved
idaes/power_generation/unit_models/helm/valve_steam.py Outdated Show resolved Hide resolved
idaes/power_generation/unit_models/helm/valve_steam.py Outdated Show resolved Hide resolved
@lbianchi-lbl
Copy link
Contributor Author

Thanks a lot @andrewlee94, this is exactly the type of review feedback I was looking for! I'll address your comments and reply or confirm the proposed fix as appropriate.

idaes/__init__.py Outdated Show resolved Hide resolved
@codecov-io
Copy link

codecov-io commented Apr 1, 2021

Codecov Report

Merging #58 (dea3540) into main (ba74001) will increase coverage by 0.00%.
The diff coverage is 46.34%.

Impacted file tree graph

@@           Coverage Diff            @@
##             main      #58    +/-   ##
========================================
  Coverage   63.78%   63.78%            
========================================
  Files         312      312            
  Lines       50261    50467   +206     
  Branches     8860     8918    +58     
========================================
+ Hits        32057    32192   +135     
- Misses      16640    16692    +52     
- Partials     1564     1583    +19     
Impacted Files Coverage Δ
idaes/core/process_base.py 87.75% <ø> (ø)
idaes/core/property_meta.py 91.01% <ø> (ø)
idaes/core/util/convergence/mpi_utils.py 42.70% <0.00%> (ø)
idaes/dmf/commands.py 0.00% <0.00%> (ø)
...s/generic_models/properties/helmholtz/helmholtz.py 87.68% <0.00%> (ø)
...rcritical_steam_cycle/supercritical_steam_cycle.py 7.46% <ø> (ø)
idaes/surrogate/helmet/Helmet.py 28.03% <ø> (ø)
idaes/surrogate/main.py 50.78% <ø> (ø)
idaes/surrogate/pysmo/polynomial_regression.py 73.08% <0.00%> (ø)
idaes/surrogate/ripe/__init__.py 100.00% <ø> (ø)
... and 31 more

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update ba74001...dea3540. Read the comment docs.

@codecov-commenter
Copy link

codecov-commenter commented May 7, 2021

Codecov Report

Merging #58 (0a65b87) into main (4c27f79) will decrease coverage by 0.00%.
The diff coverage is 42.10%.

Impacted file tree graph

@@            Coverage Diff             @@
##             main      #58      +/-   ##
==========================================
- Coverage   64.62%   64.62%   -0.01%     
==========================================
  Files         334      334              
  Lines       53767    53775       +8     
  Branches     9454     9454              
==========================================
+ Hits        34747    34752       +5     
- Misses      17282    17286       +4     
+ Partials     1738     1737       -1     
Impacted Files Coverage Δ
idaes/commands/base.py 40.00% <ø> (ø)
idaes/core/process_base.py 87.75% <ø> (ø)
idaes/core/property_meta.py 91.01% <ø> (ø)
idaes/core/util/convergence/convergence_base.py 77.57% <ø> (ø)
idaes/core/util/convergence/mpi_utils.py 42.70% <0.00%> (ø)
idaes/core/util/dyn_utils.py 72.87% <0.00%> (ø)
idaes/core/util/unit_costing.py 76.00% <0.00%> (ø)
idaes/dmf/commands.py 0.00% <0.00%> (ø)
idaes/generic_models/properties/core/eos/enrtl.py 86.07% <0.00%> (ø)
...aes/generic_models/unit_models/pressure_changer.py 79.58% <ø> (ø)
... and 23 more

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update 4c27f79...0a65b87. Read the comment docs.

@lbianchi-lbl lbianchi-lbl changed the title Pylint-specific configuration and directives to address false-positive errors Address all pylint errors in the current codebase May 7, 2021
@Robbybp
Copy link
Member

Robbybp commented May 7, 2021

  • At line 614, in copy_non_time_indexed_values: (link) (E0602) Undefined variable 'varname' (undefined-variable)

  • @Robbybp what should be used here instead of the non-existing varname?

var_src.name is the correct replacement. Should I open a PR into this branch or is it easier for you to change?

@lbianchi-lbl
Copy link
Contributor Author

  • At line 614, in copy_non_time_indexed_values: (link) (E0602) Undefined variable 'varname' (undefined-variable)

  • @Robbybp what should be used here instead of the non-existing varname?

var_src.name is the correct replacement. Should I open a PR into this branch or is it easier for you to change?

Thanks, that's perfect. I think it's best if I take care of these small changes directly here so that we don't create too many extra PRs.

Copy link
Member

@andrewlee94 andrewlee94 left a comment

Choose a reason for hiding this comment

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

@lbianchi-lbl

Re: reaction_base, line 536, the proposed fix is correct. The second instance of attr is probably a holdover from an earlier version.

Re: pressure_changer, line 620: This looks like it might be the hanging , at the end of each line. The .format() itself looks right, but for some reason there is a comma at the end of each line which is breaking up the strings.

Re: supercritical_steam_cycle: My personal response would be to give the methods unique names as I think that would be better practice and avoid any potential confusion by users. However, the power plant teams should have the final call on this (@eslickj, @MAZamarripa).

@@ -0,0 +1,173 @@
"""
Copy link
Member

Choose a reason for hiding this comment

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

Do we need to add the copyright header to this (and check any other new files too)?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Good point, I'm adding it to the dev call agenda.

ksbeattie
ksbeattie previously approved these changes Jun 10, 2021
Copy link
Member

@ksbeattie ksbeattie left a comment

Choose a reason for hiding this comment

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

Let's get this in, and keep dealing with pylint suggestions as we go.

Copy link
Member

@ksbeattie ksbeattie left a comment

Choose a reason for hiding this comment

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

LGTM

Copy link
Member

@andrewlee94 andrewlee94 left a comment

Choose a reason for hiding this comment

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

I agree, lets merge this and then look at what is left.

@ksbeattie ksbeattie merged commit fce4879 into IDAES:main Jun 10, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Priority:High High Priority Issue or PR
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Unleash pylint
7 participants