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
feature: add support for pyproject.toml configuration (#74) #84
Conversation
* upstream/master: update version update changelog add deprecation note for python versiony < 3.7 update version and history
Could you please update the PR to the current state of the code base. The file rstcheck.py no longer exists. It is now rstcheck/init.py. |
It looks like you didn't port over any of the tests for config files when you moved from test.bash to test_rstcheck.py. Do you want me to add tests for pyproject.toml or are you not planning to test the config files? |
* upstream/master: fix badges links use py 3.8 for QA workflow; pylint has issues with 3.10 resolve path before walking it update changelog skip old script tests on win and macos; they are buggy there transform paths to str update changelog silence AAA01 for old tests include extras in sdist remove old test.bash script rewrite tests from test.bash in pytest style; add new tests to tox add pytest-xdist and pytest config add pytest as testing extra move tests into own dir update if clause in CI to include PRs trigger ci on all branches for PRs remove unused/unmaintained makefile
I moved the tests into a different file: So the unit tests should go into I am currently working on rewriting or more like "reformatting" the tests in And yes. Please add tests for pyproject.toml. |
I updated the old test suite. I also added a development section to the README |
* upstream/master: add .bak files to ignore
* upstream/master: (35 commits) rename readme section to add FAQ whitespace add known limitations section; rstcheck#97 whitespace cli args take precedence over config files; fixes rstcheck#96 state config file hierarchy; rstcheck#96 update changelog change back to code-block b/c of sphinx context guard directive remove actions remove cli option for sphinx defaults remove unused noqa allow code blocks to be ignored; fixes rstcheck#79 fix types prewrap test resuls in dict for better introspection overwrite code-block and sourcecode directives without sphinx again remove code and code-block from sphinx ignore list load directives and roles from sphinx's docutils update extended default sphinx directives and roles drop sphinx default values add code-block to sphinx ignore roles ...
* upstream/master: update project rstcheck config udpate rstcheck version for pre-commit hook remove README check from tests; pre-commit does it remove wrong notice and update changelog update changelog set halt level to none/5 for docutils parse function
@Cielquan I think I have everything updated. All the tests are passing but the following QA check fails when I run
If I run Reading the pre-commit-hooks-safety repo, I see it checks all files with requirements in the name. The only files with requirements in the name are under .mypy-cache, .tox, and .venv on my end. But those should also be created when the action runs. I confirmed by deleting all three directories and re-running |
The safety issue is strange at first. The hook should not look for any requirements files because it is set it to look for pyproject.toml files only. The error you are seeing triggers when this statements is false: https://github.com/Lucas-C/pre-commit-hooks-safety/blob/master/pre_commit_hooks/safety_check.py#L40 The issue is the new Easiest solution is to change the ...
- repo: https://github.com/Lucas-C/pre-commit-hooks-safety
rev: d0c2c5156e146e5030e6aafff1a0cb398875b4f2 # frozen: v1.2.4
hooks:
- id: python-safety-dependencies-check
args: ["--full-report"]
files: pyproject.toml
+ exclude: examples
... Will now take a look at the rest of the PR. |
Thought I could propose changes with the file editing in the review. Seems not to be like this. Anyways. For the toml extra to work I added tomli to the extras section. The remaining review comments are below. |
rstcheck/__init__.py
Outdated
# separated string. This makes the options from pyproject.toml consistent | ||
# with the options read from other configuration files. The try block | ||
# accounts for pyproject files without a rstcheck section. | ||
for _ignore in ["ignore_directives", "ignore_roles", "ignore_messages", "ignore_language"]: |
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.
The report
option can also be set in a config file. Please add it to the list.
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 didn't include report
in this originally because that option can only take one value. The ignore options can take more than one and, thus, require a list in pyproject.toml. It's only these list options that cause problems and need to be converted to a comma separated string. I changed the conditional to allow any of the options to be included in pyproject.toml as a string or a list of strings. You'll see I also changed the ignore_language
option in the example pyproject.toml from ["cpp"]
to "cpp"
to cover this case.
I also wonder if recursive
and/or debug
should be added as well to completely cover all rstcheck options? Again, this would be scope creep for this PR, so I can raise an issue if you agree to the idea and want to tackle it later. Optionally, #98 could be revised to include the full scope of options you'd like the config files to support and I can make the necessary changes under that issue as well as updating the README to discuss the options.
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.
report
is a config value that is currently supported by the other config files and I think that is good. Therefore I would like pyproject.toml to do the same.
The config file is loaded for each file in their respective directory. This way you can set the report level in a nested config file without affecting files up the file tree. IMHO this is a valid reason. A corner case but valid.
recursive
makes only sense if you give a directory. Therefore it IMO makes no sense to put into a config file because you specify no files/directories here.
debug
is a special option you use actively and therefore it IMO makes also no sense to be put into a config file.
Further discussions please in a separate issue.
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 wasn't clear in my earlier comment. I included report
support in pyproject.toml from the beginning. But, because it can only be one value (info, warning, error, severe, none), there is no need for it to be a list in pyproject.toml. The ignore options can take multiple values which requires a list in pyproject.toml. The for loop, as I originally had it, was only converting the options that must be lists for multiple values in pyproject.toml to comma-separated strings.
This is what is returned when reading pyproject.toml:
{'ignore_directives': ['foobar', 'my-directive'], 'ignore_roles': ['some-custom-thing'], 'ignore_messages': ['(Document or section may not begin with a transition\\.$)'], 'ignore_language': ['cpp'], 'report': 'warning'}
This is the dict created from dict(configparser.items("rstcheck")):
{'ignore_directives': '\nfoobar,\nmy-directive,', 'ignore_roles': 'some-custom-thing', 'ignore_messages': '(Document or section may not begin with a transition\\.$)', 'ignore_language': 'cpp', 'report': 'warning'}
Since the remainder of the rstcheck functions are expecting the configparser format, the for loop is making the pyproject.toml output compatible (i.e., ['foobar', 'my-directive']
becomes 'foobar,my-directive,'
).
When adding report
to the list of options to convert, it becomes necessary to put report = ["warning"]
in pyproject.toml otherwise the for loop changes it to "w,a,r,n,i,n,g,"
. By having the conditional check if the option is a list and only convert it if so, it allows the user the option of report = ["warning"]
or report = "warning"
in pyproject.toml. This also means any of the config options can be either a list or a string which I think is less constraining on the user. It also more closely follows the format of .rstcheck.cfg in that single value options are simply report=warning
.
See also my comment below.
Ignored substitutions are internally removed in rstcheck, and this changes the length of cell contents in tables. This leads to the detection of malformed tables, which are in fact false positives. This commit solves the issue by remplacing the substitution reference by a string of the same length: "|foo|" will be replaced by "xfoox", thus preserving the length of the padding in the cells. This patch was inspired by Torbjørn Sørby (@torbsorb). Fixes: rstcheck#81
In the past, rstcheck would possibly fail to validate that tables were properly formatted when the tables contained substitution references ignored through the related configuration option. This has been fixed in a recent commit. Make sure that this remains fixed, by adding a test to validate that substitution references in tables are handled correctly. The test class contains two methods, one to check that the RST example fails to validate without the proper configuration option (substitution definitions are missing), the other asserts that the example passes all checks, including table formatting, when the missing substitutions are explicitly marked as ignored.
rstcheck/__init__.py
Outdated
@@ -479,7 +488,7 @@ def find_config( # noqa: CCR001 | |||
break | |||
directory = parent_directory | |||
|
|||
return None | |||
return "" |
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 really don't like the idea of using an empty string instead of a None
to signal that nothing was found.
And I do not see any hindrance to keep using None
(see other comments).
config_path = find_config(directory_or_file, debug=debug) | ||
if not config_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.
Early returns are a good practice especially if you have an error or nothing to do. If we have no config file we have nothing to do and can return early. Why run the other if statements too? This only increases runtime. Therefore the original early return was very good.
Here is what I would do with this function
def _get_options(directory_or_file: str, debug: bool = False) -> typing.Dict[str, str]:
config_path = find_config(directory_or_file, debug=debug)
if not config_path:
return {}
if pathlib.Path(config_path).name == "pyproject.toml":
return _get_pyproject_options(config_path)
parser = configparser.ConfigParser()
parser.read(config_path)
try:
return dict(parser.items("rstcheck"))
except configparser.NoSectionError:
return {}
Here we have early returns, which makes the code more readable IMO and shorter too. The nesting also decreases, which also improves readability.
If you use pathlib.Path or os.path does not make a big difference. I personally think pathlib.Path is more readable and I like it's API and handling more. Personal preference.
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.
My version also improves the original try/except block with the early return there.
ignore_messages = [ | ||
"(Document or section may not begin with a transition\\.$)" | ||
] | ||
ignore_language = "cpp" |
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 do not like the idea to use comma-separated-string even so we have a list type in toml at hand.
When ignore_language
is a string, why are the other ignore_* 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.
This is a must change for me.
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 only made this change to the example pyproject.toml to test both list and string for the ignore_* options.
See also my comment above, but, comma-separated-strings are allowable in pyproject.toml unless specifically prohibited by the tool from what I've seen. For example, black does the following to constrain the target-version option to a list:
target_version = config.get("target_version")
if target_version is not None and not isinstance(target_version, list):
raise click.BadOptionUsage(
"target-version", "Config key target-version must be a list"
)
Without code to constrain pyproject.toml options to lists, the savvy user might use comma-separated-strings. But the documentation for pyproject.toml implies lists are required. There is no discussion in the documentation about using comma-separated-strings as far as I know. So I think common practice would be either lists, simple strings, or integers.
I can add code to constrain options to lists, but that's going to take me longer especially this time of year (spring is planting season). But, it's starting to feel to me like all the config file code needs to be restructured (like different classes for different config file formats) which would seem to fit with your plan to split things up after v6.0.0 is released.
Anyway, I'm not trying to be difficult, I've just been conditioned that way after years in the nuclear and aerospace industries ;). Just let me know how you want to proceed.
rstcheck/__init__.py
Outdated
def _get_pyproject_options(config_path: str) -> typing.Dict[str, str]: | ||
with open(config_path, "rb") as conf_file: | ||
config = tomli.load(conf_file) | ||
options: typing.Dict[str, str] = config.get("tool", {}).get("rstcheck", {}) |
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.
Mypy cannot control this, but the type hint is missing the List[str]
doesn't it? Because the ignore_* configs are actually lists.
But see other comment before changing anything.
return options | ||
|
||
|
||
def _get_pyproject_options(config_path: str) -> typing.Dict[str, str]: |
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.
What do you think of this version for this function?
def _get_pyproject_options(config_path: str) -> typing.Dict[str, str]:
RSTCHECK_TOML_CONFIG = typing.Dict[str, typing.Union[str, typing.List[str]]]
with open(config_path, "rb") as conf_file:
config = tomli.load(conf_file)
options_from_file: typing.Optional[RSTCHECK_TOML_CONFIG] = config.get("tool", {}).get("rstcheck", None)
if options_from_file is None:
return {}
options = {}
# tomli returns a list of strings and ConfigParser returns a comma
# separated string. This makes the options from pyproject.toml consistent
# with the options read from other configuration files. The try block
# accounts for pyproject files without a rstcheck section.
for option in [
"ignore_directives",
"ignore_roles",
"ignore_messages",
"ignore_language",
"report",
]:
option_value = options_from_file.get(option, None)
if option_value is None:
continue
if isinstance(option_value, list):
option_value = ",".join(option_value)
options[option] = option_value
return options
Again early returns. I think the readability is good here with less nesting. Also the change of the try/except block to actually check the condition instead of silencing the error makes the intention more clear. This would also be another improvement one could make with my proposal for the try/except block in _get_options
above.
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.
Here I would also like to have the early return.
The rest is optional.
I appreciate your contribution and I do not think that you are difficult. I hope I am not too difficult myself. I think discussions are good and lead to better ideas and code. I like typing very much and want to be correct with it. Yes, there is the option in toml to use comma-separated-strings and lists. But IMHO lists are the only plausible option here from the typing perspective and I think most people would think that you need to specify multiple items in a list by default. Therefore I would rather forbid comma-strings in toml by the application, like you suggested. I think with the restructuring a lot of code will be changed dramatically anyways. Because of this and because I want to honor your time and effort I quickly made the adjustments, mentioned in my review above, myself and will merge them. Then I will start with the restructuring. I hope you will forgive me this rather rude invasion into your PR. And I hope to have you on board later when the new topics, which arose here, are ready to be implemented. If you have the time and want to review my restructuring, I would appreciate it too. |
* upstream/master: fix pylint issue update code like requested in review by me; see rstcheck#84
I submit for your consideration a potential solution to issue #74.
Merging this PR will:
make check
andmake test
passMy use cases (from CLI, as Makefile target, and as pre-commit hook) work when using my fork.
Closes #74