From ae216ec8ac9fa33a5b94451ed734666a927473b6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Robert?= Date: Tue, 19 May 2020 00:57:36 +0200 Subject: [PATCH 01/19] add a ytep on code style --- source/YTEPs/YTEP-0037.rst | 133 +++++++++++++++++++++++++++++++++++++ 1 file changed, 133 insertions(+) create mode 100644 source/YTEPs/YTEP-0037.rst diff --git a/source/YTEPs/YTEP-0037.rst b/source/YTEPs/YTEP-0037.rst new file mode 100644 index 0000000..3e621b2 --- /dev/null +++ b/source/YTEPs/YTEP-0037.rst @@ -0,0 +1,133 @@ +YTEP-0037: Code styling +======================= + +Abstract +-------- + +Created: May 18, 2020 +Author: Clément Robert + +This YTEP proposes the enforcement of code styling guidelines with auto-formatting tools. + + +Status +------ + +Proposed + +Project Management Links +------------------------ + +The follow PR corpus demonstrates the feasibility of the proposal + + * sorting imports with `isort `_ (`#2592 `_) + * code formatting with `black `_ (`#2596 `_) + * redirect internal imports from the wrapper module ``yt.units`` to ``unyt`` (`#2597 `_) + * add a ``pyproject.toml`` file (`#2598 `_) + * add a precommit hook configuration file (`#2600 `_) + +Detailed Description +-------------------- + +Code styling guidelines are already presented in the `project's documentation +`_, +though enforcing them is not explicitly made part of the reviewing process. +We already use ``flake8``, and integrate it to our CI to catch a subset of infractions +to PEP8. + +``black`` automatically enforces a superset of those rules, and offers very little +configuration option by design. Only the target line length can be changed. +This makes it the only point requiring discussion if this YTEP is approved. + +**maximal line length** + +The guidelines states that + + Line widths should not be more than 80 characters + +Despite this being respected in most of the code base, there remain a large amount of +outliers, that would be time-consuming to go through by hand. Taking the example of the +yt-4.0 branch at the time of writing, there are 2676 lines exceeding 80 characters (~2% +of the whole code base), or, visually + +.. image:: ../images/lw-ytep-35.png + +Note that in some cases, ``black`` will leave long lines as is. For instance, it won't +split a 200-chars long string. + +There is a range of possible values we might give preference to. Python's standard +library caps linelenght at 79, pandas does so at 80. By default, ``black`` will target +88, as its authors claim it reduces the total number of lines by some 10% (as compared +to enforcing 80). + +In first drafting the PR linked above, I chose a line-lenght of 100, so as to minimize +the amount of manual tweaking left to me after a ``black`` pass. +I estimated that imposing a strict limit to 80 chars would leave 4320 lines to be +manually updated, while caping at 88 leaves a mere ... 1341 (still a 75% less work :-)). + + +**sorting imports** + +PEP8 `recommends sorting imports statments `_, +Needless to say, the task is daunting and definitely not worthy of anyone's time if we +had to go back and apply those rules manually to the code base. +Luckilly, ``isort`` is able to check for and auto-apply those rules, so it can easily be +added to the CI-linting process. + +Moreover, ``isort`` is configurable so that it allows for the definition of custom +"sections" within import statements. This can be use to isolate imports from ``unyt``, +which falls somewhere in between the default sections "third party" (external +dependency) and "first party" (the project). This is done in +`#2592 `_. + +To better highlight the way yt 4.0 depends on ``unyt``, I also propose that, within the +code base, we import directly from ``unyt`` as often as possible, so as to limit +confusion between ``unyt`` and its wrapper interface ``yt.units``. This is done in +`#2597 `_. + + +**Side effects** + +Although some default options in ``isort`` conflict with ``black``'s opinated standard, +it can be configured so that the tools play nicely with each other. +This is demonstrated in `#2596 `_ where both +check pass on Travis. + +On another note, black only recognizes ``pyproject.toml`` as a configuration file (and +is explicitly not planning to support other files such as ``setup.cfg``). +An undesirable effect of using ``pyproject.toml`` solely as a configuration file for +``black`` is that ``pip`` will detect it and change its behaviour when its present. The +correct way to introduce this file is by specifying yt's build requirements within it. +A proof of concept for this is `#2598 `_, +where CI builds are run correctly across all tested python versions (3.6, 3.7, 3.8). + +A serious counter-argument to applying black is that it implies messing up with ``git +blame`` by making a single contributor the defacto last-author of a large number of +lines they have not even necessarily read. Most recent versions of ``git`` can be +configured to ignore specific commits in ``git blame``. However, ``black``'s own README +currently points out that github's UI for ``git-blame`` does not support this feature +(yet ?). + + +**outreach** + +Enforcing these change throughout future contributions can be done by + +* updating the Developper Guide (done in part in `#2592 `_) +* offering a precommit hook configuration file to help contributors automate the linting stage locally (``precommit_hook.yaml``) +such a configuration file is propoed in `#2600 `_ +Backwards Compatibility +----------------------- + +Yes. + +Alternatives +------------ + +* Enforcing styling guidelines through pier review for each PR. Obviously this is a + lot more work. Additionally, this methodology is prone to error and may cause delay in + the PR approval process in case the authors disagree with the reviewers on the + application of styling rules. +* Leaving code style decisions up to authors, and embracing the style diversity. + + From 99e349daff8fc1b6dd0da676d7327dc0df75eb8c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Robert?= Date: Tue, 19 May 2020 18:06:11 +0200 Subject: [PATCH 02/19] add missing image file --- source/images/lw-ytep-37.png | Bin 0 -> 57807 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 source/images/lw-ytep-37.png diff --git a/source/images/lw-ytep-37.png b/source/images/lw-ytep-37.png new file mode 100644 index 0000000000000000000000000000000000000000..25924d234f984dcdc9f95da579d522600892cea9 GIT binary patch literal 57807 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 literal 0 HcmV?d00001 From 5f8659f9976c9b83c978f756d9046b0faff7704d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Robert?= Date: Wed, 20 May 2020 00:04:37 +0200 Subject: [PATCH 03/19] typo Co-authored-by: Matthew Turk --- source/YTEPs/YTEP-0037.rst | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/source/YTEPs/YTEP-0037.rst b/source/YTEPs/YTEP-0037.rst index 3e621b2..75a8d87 100644 --- a/source/YTEPs/YTEP-0037.rst +++ b/source/YTEPs/YTEP-0037.rst @@ -124,10 +124,9 @@ Yes. Alternatives ------------ -* Enforcing styling guidelines through pier review for each PR. Obviously this is a +* Enforcing styling guidelines through peer review for each PR. Obviously this is a lot more work. Additionally, this methodology is prone to error and may cause delay in the PR approval process in case the authors disagree with the reviewers on the application of styling rules. * Leaving code style decisions up to authors, and embracing the style diversity. - From d3cb3033f2b40d34360ef6eca7efdfded8b4a49a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Robert?= Date: Wed, 20 May 2020 12:12:21 +0200 Subject: [PATCH 04/19] add insight on Cython files, define merging strategy for existing PRs, fix the image insertion --- source/YTEPs/YTEP-0037.rst | 28 +++++++++++++++++++++++++--- 1 file changed, 25 insertions(+), 3 deletions(-) diff --git a/source/YTEPs/YTEP-0037.rst b/source/YTEPs/YTEP-0037.rst index 75a8d87..0f316db 100644 --- a/source/YTEPs/YTEP-0037.rst +++ b/source/YTEPs/YTEP-0037.rst @@ -50,7 +50,7 @@ outliers, that would be time-consuming to go through by hand. Taking the example yt-4.0 branch at the time of writing, there are 2676 lines exceeding 80 characters (~2% of the whole code base), or, visually -.. image:: ../images/lw-ytep-35.png +.. image:: ../images/lw-ytep-37.png Note that in some cases, ``black`` will leave long lines as is. For instance, it won't split a 200-chars long string. @@ -107,15 +107,37 @@ lines they have not even necessarily read. Most recent versions of ``git`` can b configured to ignore specific commits in ``git blame``. However, ``black``'s own README currently points out that github's UI for ``git-blame`` does not support this feature (yet ?). - -**outreach** +It should be noted that ``black`` does not have a parser for Cython files, but +interstingly ``flake8`` and ``isort`` do. Thus it is possible to add style checks for +Cython extensions to the CI pipeline. + +**outreach and transition** Enforcing these change throughout future contributions can be done by * updating the Developper Guide (done in part in `#2592 `_) * offering a precommit hook configuration file to help contributors automate the linting stage locally (``precommit_hook.yaml``) such a configuration file is propoed in `#2600 `_ + +It is expected that transitioning to the "blackened" version of the code will add a bit +of overhead in merging pre-existing PRs. Specifically, a simple ``git merge +`` will almost certainly raise git conflicts. A potential +solution to this is to sanitize the pr-branch (on author side) with: + +.. code:: shell + + pip install black + black --line-width yt/ + git merge --strategy ours + git push + +I tested this strategy locally by simulating blackening at an arbitrary point in the +past and merging the current state of the code base back in, producing a net zero diff +with a direct blackening of the current state. In practive I advise caution, and +sanitized code should be reviewed before merging. + + Backwards Compatibility ----------------------- From 46bfd2590c5f506a69911b46216d5a7b181288c2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Robert?= Date: Wed, 20 May 2020 12:20:01 +0200 Subject: [PATCH 05/19] typo --- source/YTEPs/YTEP-0037.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/YTEPs/YTEP-0037.rst b/source/YTEPs/YTEP-0037.rst index 0f316db..0e70db4 100644 --- a/source/YTEPs/YTEP-0037.rst +++ b/source/YTEPs/YTEP-0037.rst @@ -134,7 +134,7 @@ solution to this is to sanitize the pr-branch (on author side) with: I tested this strategy locally by simulating blackening at an arbitrary point in the past and merging the current state of the code base back in, producing a net zero diff -with a direct blackening of the current state. In practive I advise caution, and +with a direct blackening of the current state. In practice I advise caution, and sanitized code should be reviewed before merging. From 1014f819e587c89cbbd8f85ee43f0f82cd72cbcc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Robert?= Date: Wed, 20 May 2020 12:43:53 +0200 Subject: [PATCH 06/19] typo Co-authored-by: Britton Smith --- source/YTEPs/YTEP-0037.rst | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/source/YTEPs/YTEP-0037.rst b/source/YTEPs/YTEP-0037.rst index 0e70db4..dc36371 100644 --- a/source/YTEPs/YTEP-0037.rst +++ b/source/YTEPs/YTEP-0037.rst @@ -18,7 +18,7 @@ Proposed Project Management Links ------------------------ -The follow PR corpus demonstrates the feasibility of the proposal +The following PR corpus demonstrates the feasibility of the proposal * sorting imports with `isort `_ (`#2592 `_) * code formatting with `black `_ (`#2596 `_) @@ -151,4 +151,3 @@ Alternatives the PR approval process in case the authors disagree with the reviewers on the application of styling rules. * Leaving code style decisions up to authors, and embracing the style diversity. - From fc03b1b5a5d3d4716b5b06125122071c5a0f21a1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Robert?= Date: Thu, 21 May 2020 11:54:44 +0200 Subject: [PATCH 07/19] add roadmap, mention docstring issue, and add bugbear --- source/YTEPs/YTEP-0037.rst | 72 ++++++++++++++++++++++++++++++++++---- 1 file changed, 66 insertions(+), 6 deletions(-) diff --git a/source/YTEPs/YTEP-0037.rst b/source/YTEPs/YTEP-0037.rst index dc36371..023ae0f 100644 --- a/source/YTEPs/YTEP-0037.rst +++ b/source/YTEPs/YTEP-0037.rst @@ -32,12 +32,27 @@ Detailed Description Code styling guidelines are already presented in the `project's documentation `_, though enforcing them is not explicitly made part of the reviewing process. -We already use ``flake8``, and integrate it to our CI to catch a subset of infractions -to PEP8. -``black`` automatically enforces a superset of those rules, and offers very little -configuration option by design. Only the target line length can be changed. -This makes it the only point requiring discussion if this YTEP is approved. +We already use ``flake8`` and integrate it to our CI to catch a subset of +infractions to `PEP 8 `_. From +``flake8``'s `pipy page `_ + + Flake8 is a wrapper around these tools: + - PyFlakes + - pycodestyle + - Ned Batchelder’s McCabe script + +From ``black``'s `documentation `_ + + The rules for horizontal whitespace can be summarized as: do whatever makes + pycodestyle happy. + (...) + The coding style used by Black can be viewed as a strict subset of PEP 8. + +so it is expected that ``black`` plays nicely with ``flake8`` by consruction. +``black`` applies an opinated style, and offers very little configuration option +by design. Only the target line length can be changed. This makes it a critical +point, requiring discussion if this YTEP is approved. **maximal line length** @@ -86,7 +101,16 @@ confusion between ``unyt`` and its wrapper interface ``yt.units``. This is done `#2597 `_. -**Side effects** +**additional rules** + +It has already been the case that some coding bad-practice were not detecte by +``flake8``. For instance, bare except statements (see `2474 `_ +) + +`flake8-bugbear `_ is a ``flake8`` +pluggin that adds warnings to this problem and more. + +**side effects** Although some default options in ``isort`` conflict with ``black``'s opinated standard, it can be configured so that the tools play nicely with each other. @@ -112,6 +136,13 @@ It should be noted that ``black`` does not have a parser for Cython files, but interstingly ``flake8`` and ``isort`` do. Thus it is possible to add style checks for Cython extensions to the CI pipeline. +Additionally, ``black`` will not force line-lenght limits in docstrings. +``flake8`` will still be able to catch violations there, but solving them +require manual tweaking. However, the amount of existing docstrings going over +88 characters is fairly small (a few dozens), so this is by no means a blocking +condition. + + **outreach and transition** Enforcing these change throughout future contributions can be done by @@ -138,6 +169,35 @@ with a direct blackening of the current state. In practice I advise caution, and sanitized code should be reviewed before merging. +The shorter the transition, the easier, so I think that most of the PRs could be +merged in a very narrow time window (a day or two), provided the appropriate +conditions. However, because we want to ensure that each step passes the tests, +which typically takes a least an hour or two per step, I propose that prep steps +be done separately, and the big one (blackening) happen on a meeting. + +A possible roadmap +------------------ + +**pre meeting** + +* settle on a maximal line length and the status of unyt ("second" or third party) +* merge isort pass on the code base + CI check + doc +* optional (needs approval) merge `#2597 `_ +* merge (needs tweaking) `#2598 `_ +* rebase blackening PR on the target branch (yt-4.0 ?) and prepare it with agreed line length +* provided all CI check pass and the PR is reviewed & approved, this goes to a PR triage meeting + +**on the meeting** + +* merge blackening + manual fixups + CI checks + doc +* signal to open PR authors that they should apply black (see transitioning strategy) + +**can be done later** + +* merge `#2600 `_ +* merge `#2595 `_ + + Backwards Compatibility ----------------------- From d60d22e8f23f5b27c0a44bc9db77a9d553624227 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Robert?= Date: Thu, 21 May 2020 14:20:37 +0200 Subject: [PATCH 08/19] revise estimations in number of lines requiring manual update (10 times less than previous estimation !) --- source/YTEPs/YTEP-0037.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/source/YTEPs/YTEP-0037.rst b/source/YTEPs/YTEP-0037.rst index 023ae0f..bddca23 100644 --- a/source/YTEPs/YTEP-0037.rst +++ b/source/YTEPs/YTEP-0037.rst @@ -77,8 +77,8 @@ to enforcing 80). In first drafting the PR linked above, I chose a line-lenght of 100, so as to minimize the amount of manual tweaking left to me after a ``black`` pass. -I estimated that imposing a strict limit to 80 chars would leave 4320 lines to be -manually updated, while caping at 88 leaves a mere ... 1341 (still a 75% less work :-)). +I estimated that imposing a strict limit to 80 chars would leave 468 lines to be +manually updated, while caping at 88 leaves a mere 116 (75% less work :-)). **sorting imports** From 6948754011a27d69e52b92f5724569ddc0a16603 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Robert?= Date: Thu, 21 May 2020 16:44:22 +0200 Subject: [PATCH 09/19] detail on bugbear --- source/YTEPs/YTEP-0037.rst | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/source/YTEPs/YTEP-0037.rst b/source/YTEPs/YTEP-0037.rst index bddca23..092e675 100644 --- a/source/YTEPs/YTEP-0037.rst +++ b/source/YTEPs/YTEP-0037.rst @@ -103,12 +103,21 @@ confusion between ``unyt`` and its wrapper interface ``yt.units``. This is done **additional rules** -It has already been the case that some coding bad-practice were not detecte by -``flake8``. For instance, bare except statements (see `2474 `_ -) - `flake8-bugbear `_ is a ``flake8`` -pluggin that adds warnings to this problem and more. +pluggin that goes beyond code style and detect some additional anti-patterns, +most of which can be regarded as highly suspected design flaws in otherwise +syntaxically valid statement. +For instance, it will catch mutable default values such as in + +.. code:: python + + def spam(a={}, b=[]): + print("bacon") + +Which is a well known "gotcha", as documented for instance `here `_ +In short, this plugin detects bugs that went under the radar up to now, so it's +probably worth adding it to our linting CI. + **side effects** @@ -196,6 +205,7 @@ A possible roadmap * merge `#2600 `_ * merge `#2595 `_ +* reduce flake8 ignore list, add bugbear plugin and correct detected anti-patterns Backwards Compatibility From 4899614f65be18d235c0584fcad524ddb68b376c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Robert?= Date: Thu, 21 May 2020 16:55:26 +0200 Subject: [PATCH 10/19] clarify bugbear example --- source/YTEPs/YTEP-0037.rst | 23 +++++++++++++++++------ 1 file changed, 17 insertions(+), 6 deletions(-) diff --git a/source/YTEPs/YTEP-0037.rst b/source/YTEPs/YTEP-0037.rst index 092e675..003bccc 100644 --- a/source/YTEPs/YTEP-0037.rst +++ b/source/YTEPs/YTEP-0037.rst @@ -104,17 +104,28 @@ confusion between ``unyt`` and its wrapper interface ``yt.units``. This is done **additional rules** `flake8-bugbear `_ is a ``flake8`` -pluggin that goes beyond code style and detect some additional anti-patterns, -most of which can be regarded as highly suspected design flaws in otherwise -syntaxically valid statement. -For instance, it will catch mutable default values such as in +pluggin that goes beyond code style and detects some additional anti-patterns, +most of which are correspond very likely to design flaws in otherwise +syntaxically valid statement. For instance, it will catch mutable default values +such as in .. code:: python def spam(a={}, b=[]): - print("bacon") + #... -Which is a well known "gotcha", as documented for instance `here `_ +which, in most context, should be rewritten as + +.. code:: python + + def spam(a=None, b=None): + if a is None: + a = {} + if b is None: + b = [] + +This is a well known "gotcha", as documented for instance +`here `_. In short, this plugin detects bugs that went under the radar up to now, so it's probably worth adding it to our linting CI. From c8fd9e6141328859b9026add0b1106c2f229b6df Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Robert?= Date: Thu, 21 May 2020 18:53:53 +0200 Subject: [PATCH 11/19] add detail on flake8-docstrings --- source/YTEPs/YTEP-0037.rst | 25 +++++++++++++++++++------ 1 file changed, 19 insertions(+), 6 deletions(-) diff --git a/source/YTEPs/YTEP-0037.rst b/source/YTEPs/YTEP-0037.rst index 003bccc..8f686cc 100644 --- a/source/YTEPs/YTEP-0037.rst +++ b/source/YTEPs/YTEP-0037.rst @@ -101,13 +101,12 @@ confusion between ``unyt`` and its wrapper interface ``yt.units``. This is done `#2597 `_. -**additional rules** +**additional rules (flake8 pluggings)** -`flake8-bugbear `_ is a ``flake8`` -pluggin that goes beyond code style and detects some additional anti-patterns, -most of which are correspond very likely to design flaws in otherwise -syntaxically valid statement. For instance, it will catch mutable default values -such as in +`flake8-bugbear `_ is a ``flake8`` pluggin that +goes beyond code style and detects some additional anti-patterns, most of which are +correspond very likely to design flaws in otherwise syntaxically valid statement. For +instance, it will catch mutable default values such as in .. code:: python @@ -130,6 +129,20 @@ In short, this plugin detects bugs that went under the radar up to now, so it's probably worth adding it to our linting CI. +Another plugging can be added to enforce docstring formatting +(`flake8-docstrings `_), and has a +straight-forward option configuration to validate docstrings are numpy-styled. However, +there is currently a very large debt in errors caught by this tool, and no way to +automatically solve them. However, it could still be added to our linting CI, if check +for *new* errors only, such as + +.. code:: bash + + git diff upstream/master -u -- "*.py" | flake8 --diff + +(snippet borrowed from pandas' contributing guide) + + **side effects** Although some default options in ``isort`` conflict with ``black``'s opinated standard, From de21c105e86ddf4e54f9b2df90667e5a5a015ac3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Robert?= Date: Thu, 21 May 2020 19:33:24 +0200 Subject: [PATCH 12/19] typos + rephrasing --- source/YTEPs/YTEP-0037.rst | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/source/YTEPs/YTEP-0037.rst b/source/YTEPs/YTEP-0037.rst index 8f686cc..da07e4e 100644 --- a/source/YTEPs/YTEP-0037.rst +++ b/source/YTEPs/YTEP-0037.rst @@ -49,8 +49,8 @@ From ``black``'s `documentation `_. - +.. _additional rules: **additional rules (flake8 pluggings)** -`flake8-bugbear `_ is a ``flake8`` pluggin that -goes beyond code style and detects some additional anti-patterns, most of which are +`flake8-bugbear `_ is a ``flake8`` plugging +that goes beyond code style and detects some additional anti-patterns, most of which are correspond very likely to design flaws in otherwise syntaxically valid statement. For instance, it will catch mutable default values such as in @@ -113,7 +115,7 @@ instance, it will catch mutable default values such as in def spam(a={}, b=[]): #... -which, in most context, should be rewritten as +which, in most contexts, should be rewritten as .. code:: python From 7335e10f74fdf081aea4e6c7c7bad5da64141912 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Robert?= Date: Fri, 22 May 2020 18:10:04 +0200 Subject: [PATCH 13/19] fix estimations --- source/YTEPs/YTEP-0037.rst | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/source/YTEPs/YTEP-0037.rst b/source/YTEPs/YTEP-0037.rst index da07e4e..f4abb11 100644 --- a/source/YTEPs/YTEP-0037.rst +++ b/source/YTEPs/YTEP-0037.rst @@ -62,8 +62,8 @@ The guidelines states that Despite this being respected in most of the code base, there remains a large amount of outliers, that would be time-consuming to go through by hand. Taking the example of the -yt-4.0 branch at the time of writing, there are 2676 lines exceeding 80 characters (~2% -of the whole code base), or, visually +yt-4.0 branch at the time of writing, there are 2158 lines exceeding 80 characters +(~1.5% of the whole code base), or, visually .. image:: ../images/lw-ytep-37.png @@ -79,8 +79,8 @@ to enforcing 80). In first drafting the PR linked above, I chose a line-lenght of 100, so as to minimize the amount of manual tweaking left to me after a ``black`` pass. -I estimated that imposing a strict limit to 80 chars would leave 468 lines to be -manually updated, while caping at 88 leaves a mere 116 (75% less work :-)). +I estimated that imposing a strict limit to 80 chars would leave 545 lines to be +manually updated, while caping at 88 leaves a mere 135 (75% less work). **sorting imports** From 7db39fd9ea27391d69c8f84952675a830c5e8ff3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Robert?= Date: Sat, 23 May 2020 14:27:14 +0200 Subject: [PATCH 14/19] add note about dask's configuration for black --- source/YTEPs/YTEP-0037.rst | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/source/YTEPs/YTEP-0037.rst b/source/YTEPs/YTEP-0037.rst index f4abb11..dd3569b 100644 --- a/source/YTEPs/YTEP-0037.rst +++ b/source/YTEPs/YTEP-0037.rst @@ -78,9 +78,11 @@ library caps linelenght at 79, pandas does so at 80. By default, ``black`` will to enforcing 80). In first drafting the PR linked above, I chose a line-lenght of 100, so as to minimize -the amount of manual tweaking left to me after a ``black`` pass. -I estimated that imposing a strict limit to 80 chars would leave 545 lines to be -manually updated, while caping at 88 leaves a mere 135 (75% less work). +the amount of manual tweaking left to me after a ``black`` pass. I estimated that +imposing a strict limit to 80 chars would leave 545 lines to be manually updated, while +caping at 88 leaves a mere 135 (75% less work). As a reference, ``dask`` uses black's +default settings, *and* allows flexibility for docstrings up to 120 characters through +flake8. **sorting imports** From b1481f4a6332b6be8e0c10d0aa578ec56ead4013 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Robert?= Date: Sat, 23 May 2020 16:13:22 +0200 Subject: [PATCH 15/19] add subsection for flynt --- source/YTEPs/YTEP-0037.rst | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/source/YTEPs/YTEP-0037.rst b/source/YTEPs/YTEP-0037.rst index dd3569b..ce350c9 100644 --- a/source/YTEPs/YTEP-0037.rst +++ b/source/YTEPs/YTEP-0037.rst @@ -25,6 +25,7 @@ The following PR corpus demonstrates the feasibility of the proposal * redirect internal imports from the wrapper module ``yt.units`` to ``unyt`` (`#2597 `_) * add a ``pyproject.toml`` file (`#2598 `_) * add a precommit hook configuration file (`#2600 `_) + * apply fstrings everywhere (`#2605 `_) Detailed Description -------------------- @@ -105,7 +106,14 @@ confusion between ``unyt`` and its wrapper interface ``yt.units``. This is done `#2597 `_. .. _additional rules: -**additional rules (flake8 pluggings)** +**additional rules & flake8 pluggings** + +Since the oldest python version supported (as of yt-4.0) is 3.6, it means we can start +using fstrings instead of ``str.format()`` and ``%`` formatting. +`#2605 `_ demonstrates how a transition can +be performed using ``flynt``. + +-- `flake8-bugbear `_ is a ``flake8`` plugging that goes beyond code style and detects some additional anti-patterns, most of which are @@ -132,6 +140,7 @@ This is a well known "gotcha", as documented for instance In short, this plugin detects bugs that went under the radar up to now, so it's probably worth adding it to our linting CI. +-- Another plugging can be added to enforce docstring formatting (`flake8-docstrings `_), and has a @@ -147,6 +156,7 @@ for *new* errors only, such as (snippet borrowed from pandas' contributing guide) + **side effects** Although some default options in ``isort`` conflict with ``black``'s opinated standard, From abb6797512a317d3910fe8d6bd24f04577d3e5b5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Robert?= Date: Fri, 5 Jun 2020 21:37:50 +0200 Subject: [PATCH 16/19] correct typo --- source/YTEPs/YTEP-0037.rst | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/source/YTEPs/YTEP-0037.rst b/source/YTEPs/YTEP-0037.rst index ce350c9..c67340b 100644 --- a/source/YTEPs/YTEP-0037.rst +++ b/source/YTEPs/YTEP-0037.rst @@ -74,11 +74,11 @@ This is most important in the case of dosctrings, and I explore the tools availa validate them hereafter (see `additional rules`_). There is a range of possible values we might give preference to. Python's standard -library caps linelenght at 79, pandas does so at 80. By default, ``black`` will target +library caps line-length at 79, pandas does so at 80. By default, ``black`` will target 88, as its authors claim it reduces the total number of lines by some 10% (as compared to enforcing 80). -In first drafting the PR linked above, I chose a line-lenght of 100, so as to minimize +In first drafting the PR linked above, I chose a line-length of 100, so as to minimize the amount of manual tweaking left to me after a ``black`` pass. I estimated that imposing a strict limit to 80 chars would leave 545 lines to be manually updated, while caping at 88 leaves a mere 135 (75% less work). As a reference, ``dask`` uses black's @@ -183,7 +183,7 @@ It should be noted that ``black`` does not have a parser for Cython files, but interstingly ``flake8`` and ``isort`` do. Thus it is possible to add style checks for Cython extensions to the CI pipeline. -Additionally, ``black`` will not force line-lenght limits in docstrings. +Additionally, ``black`` will not force line-length limits in docstrings. ``flake8`` will still be able to catch violations there, but solving them require manual tweaking. However, the amount of existing docstrings going over 88 characters is fairly small (a few dozens), so this is by no means a blocking From 9d74e29efa24891ddf3446f447af0b1086f9df17 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Robert?= Date: Fri, 5 Jun 2020 21:39:04 +0200 Subject: [PATCH 17/19] correct statement about pandas + black --- source/YTEPs/YTEP-0037.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/YTEPs/YTEP-0037.rst b/source/YTEPs/YTEP-0037.rst index c67340b..d6e118a 100644 --- a/source/YTEPs/YTEP-0037.rst +++ b/source/YTEPs/YTEP-0037.rst @@ -74,7 +74,7 @@ This is most important in the case of dosctrings, and I explore the tools availa validate them hereafter (see `additional rules`_). There is a range of possible values we might give preference to. Python's standard -library caps line-length at 79, pandas does so at 80. By default, ``black`` will target +library caps line-length at 79, pandas does so at 88. By default, ``black`` will target 88, as its authors claim it reduces the total number of lines by some 10% (as compared to enforcing 80). From 76abbad4163cc5e166fd8d2cf910d0ea989527dd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Robert?= Date: Tue, 23 Jun 2020 00:36:01 +0200 Subject: [PATCH 18/19] add intro to roadmap (contributed by Madicken) --- source/YTEPs/YTEP-0037.rst | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/source/YTEPs/YTEP-0037.rst b/source/YTEPs/YTEP-0037.rst index d6e118a..adcb7d9 100644 --- a/source/YTEPs/YTEP-0037.rst +++ b/source/YTEPs/YTEP-0037.rst @@ -225,6 +225,12 @@ be done separately, and the big one (blackening) happen on a meeting. A possible roadmap ------------------ +To ensure cohesion in getting the number of features included in this PR in the +codebase, we will have a dedicated maintainer/triage meeting. This YTEP's PR, the yt +slack, and the yt-dev mailing list will include the meeting details for interested +parties to attend. Some items require completion before the triage meeting, and some can +be done afterwards, and have been categorized below. + **pre meeting** * settle on a maximal line length and the status of unyt ("second" or third party) From 1106102ab9cb98a84c9dcb521070c0800458aac0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Robert?= Date: Fri, 26 Jun 2020 18:22:49 +0200 Subject: [PATCH 19/19] change YTEP0037 status to accepted, remove unyt import proposal --- source/YTEPs/YTEP-0037.rst | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/source/YTEPs/YTEP-0037.rst b/source/YTEPs/YTEP-0037.rst index adcb7d9..abecdaf 100644 --- a/source/YTEPs/YTEP-0037.rst +++ b/source/YTEPs/YTEP-0037.rst @@ -13,7 +13,7 @@ This YTEP proposes the enforcement of code styling guidelines with auto-formatti Status ------ -Proposed +Accepted Project Management Links ------------------------ @@ -22,7 +22,6 @@ The following PR corpus demonstrates the feasibility of the proposal * sorting imports with `isort `_ (`#2592 `_) * code formatting with `black `_ (`#2596 `_) - * redirect internal imports from the wrapper module ``yt.units`` to ``unyt`` (`#2597 `_) * add a ``pyproject.toml`` file (`#2598 `_) * add a precommit hook configuration file (`#2600 `_) * apply fstrings everywhere (`#2605 `_) @@ -100,11 +99,6 @@ which falls somewhere in between the default sections "third party" (external dependency) and "first party" (the project). This is done in `#2592 `_. -To better highlight the way yt 4.0 depends on ``unyt``, I also propose that, within the -code base, we import directly from ``unyt`` as often as possible, so as to limit -confusion between ``unyt`` and its wrapper interface ``yt.units``. This is done in -`#2597 `_. - .. _additional rules: **additional rules & flake8 pluggings** @@ -233,7 +227,13 @@ be done afterwards, and have been categorized below. **pre meeting** -* settle on a maximal line length and the status of unyt ("second" or third party) +* settle on a maximal line length and the status of ``unyt`` ("second" or third party) + + +.. note:: + + final: maximum-line-length 88, ``unyt`` treated as third party + * merge isort pass on the code base + CI check + doc * optional (needs approval) merge `#2597 `_ * merge (needs tweaking) `#2598 `_