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

Annotate primary requirements and VCS dependencies #1058

Merged
merged 1 commit into from
Feb 20, 2020

Conversation

AndydeCleyre
Copy link
Contributor

@AndydeCleyre AndydeCleyre commented Feb 7, 2020

Resolves #881
Resolves #293

This change brings annotations to primary requirements in the compilation output.

The annotation may be merely a reqs-in source:

django-debug-toolbar==2.2  # via -r requirements.in (line 2)

or it may additionally include reverse dependencies:

django==3.0.3             # via -r requirements.in (line 1), django-debug-toolbar

Existing tests are modified to either adjust their expectations, or compile with --no-annotations if annotations are irrelevant. Two tests have been inverted and renamed:

-test_format_requirement_not_for_primary
+test_format_requirement_for_primary
-test_format_requirement_not_for_primary_lower_case
+test_format_requirement_for_primary_lower_case

Changelog-friendly one-liner: Primary requirements and VCS dependencies now get annotated with any source .in files and reverse dependencies

Contributor checklist
  • Provided the tests for the changes.
  • Gave a clear one-line description in the PR (that the maintainers can add to CHANGELOG.md on release).
  • Assign the PR to an existing or new milestone for the target version (following Semantic Versioning).

Please review these changes with the following questions in mind:

  1. Is the new annotation string as desired?
  2. Is it desirable to add an option to disable this new behavior?
  3. Should the non-annotation-focused tests needing modification be, generally, expecting an exact annotation? Or should we simply use --no-annotate?
  4. In which cases might an InstallRequirement's comes_from attribute be a str, or an InstallRequirement? The following alternatives seem to result in the same output. Which is saner or preferred, or handles potential edge cases better?
 required_by |= {
     src_ireq.comes_from
-    for src_ireq in ireq._source_ireqs
     if isinstance(src_ireq.comes_from, str)
+    else src_ireq.comes_from.name.lower()
+    for src_ireq in ireq._source_ireqs
+    if src_ireq.comes_from
 }

code in context

  1. If/when all looks good, want it squashed?

@codecov
Copy link

codecov bot commented Feb 7, 2020

Codecov Report

Merging #1058 into master will increase coverage by 0.12%.
The diff coverage is n/a.

Impacted file tree graph

@@            Coverage Diff             @@
##           master    #1058      +/-   ##
==========================================
+ Coverage   99.35%   99.47%   +0.12%     
==========================================
  Files          34       34              
  Lines        2463     2476      +13     
  Branches      307      312       +5     
==========================================
+ Hits         2447     2463      +16     
+ Misses          8        6       -2     
+ Partials        8        7       -1     
Impacted Files Coverage Δ
piptools/repositories/pypi.py 95.16% <0.00%> (+1.44%) ⬆️

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 f2e2e2b...ca69a92. Read the comment docs.

@AndydeCleyre AndydeCleyre changed the title Annotate primary requirements Annotate primary requirements; fixes #881 Feb 7, 2020
@graingert
Copy link
Member

The output looks great! I don't think this should introduce a new setting though

Copy link
Member

@atugushev atugushev left a comment

Choose a reason for hiding this comment

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

The idea and approach are awesome! I've suggested some improvements below:

piptools/writer.py Outdated Show resolved Hide resolved
@atugushev
Copy link
Member

atugushev commented Feb 8, 2020

@AndydeCleyre

  1. Is the new annotation string as desired?

It looks great! 👍

  1. Is it desirable to add an option to disable this new behavior?

This improvement is pretty useful, no need to disable it. Otherwise --no-annotate helps.

  1. Should the non-annotation-focused tests needing modification be, generally, expecting an exact annotation? Or should we simply use --no-annotate?

I think it's okay to disable annotations to focus on certain things desired to test.

  1. In which cases might an InstallRequirement's comes_from attribute be a str, or an InstallRequirement? The following alternatives seem to result in the same output. Which is saner or preferred, or handles potential edge cases better?

I'd prefer the same way as pip does.

  1. If/when all looks good, want it squashed?

Yes, better to squash.

@atugushev atugushev changed the title Annotate primary requirements; fixes #881 Annotate primary requirements Feb 8, 2020
@atugushev atugushev added the enhancement Improvements to functionality label Feb 8, 2020
@atugushev
Copy link
Member

atugushev commented Feb 8, 2020

@AndydeCleyre

4. In which cases might an InstallRequirement's comes_from attribute be a str, or an InstallRequirement?

Usually, if a requirement came from a nested requirement file (included via -r), see related code in pip.

@AndydeCleyre AndydeCleyre force-pushed the bugfix/881 branch 3 times, most recently from 8184f16 to 638ceab Compare February 10, 2020 20:06
@atugushev
Copy link
Member

atugushev commented Feb 11, 2020

@AndydeCleyre

I've noticed that pip-compile setup.py outputs temporary file name. See:

$ pip-compile setup.py
#
# This file is autogenerated by pip-compile
# To update, run:
#
#    pip-compile setup.py
#
click==7.0                # via -r /var/folders/4x/rz_w89dx1z3bydfd6qpdjn_h0000gn/T/tmp10jejvdm (line 1)
six==1.14.0               # via -r /var/folders/4x/rz_w89dx1z3bydfd6qpdjn_h0000gn/T/tmp10jejvdm (line 2)

Have no idea how to fix this yet 🤔

@atugushev
Copy link
Member

atugushev commented Feb 11, 2020

Probably, we could enrich those constraints somehow with ireq.comes_from = dist.get_name()

constraints.extend(
parse_requirements(
tmpfile.name,
finder=repository.finder,
session=repository.session,
options=repository.options,
)
)

@AndydeCleyre
Copy link
Contributor Author

Thank you @atugushev, good catch. I'll look into this today.

@atugushev atugushev changed the title Annotate primary requirements Annotate primary requirements and VCS dependencies Feb 11, 2020
@AndydeCleyre
Copy link
Contributor Author

My proposed solution will also handle reqin as stdin.

@atugushev
Copy link
Member

@AndydeCleyre

My proposed solution will also handle reqin as stdin.

Which one?

For the stdin it could be ireq.comes_from = "-r /dev/stdin", but would look strange for windows 😄

@graingert
Copy link
Member

-r -?

@AndydeCleyre
Copy link
Contributor Author

AndydeCleyre commented Feb 11, 2020

Which one?

Sorry, I meant "my proposal [which will exist] will . . ."

-r - as @graingert suggests looks good to me for stdin.

For setup.py, yes dist.get_name() should be included in the string, as well as setup.py. It should also not be ambiguous with any repo package names. Some options for the format (supposing dist.get_name() == 'pip-tools'):

  1. -r setup.py (pip-tools)
  2. -r setup.py: pip-tools
  3. pip-tools setup.py
  4. pip-tools: setup.py
  5. pip-tools (setup.py)
  6. setup.py (pip-tools)

I'm leaning toward 5: <pkgname> (setup.py) as it seems to most closely match existing annotations; if <pkgname> is included, then it is similar to listing an external dependency, with some more detail. But 1 is also very logical, and 6 has some clear merit to me as well.

Which looks best to you, @atugushev and @graingert ?


New questions though:

I thought that setup.py should be rel/path/to/setup.py if it's not adjacent to the output file. But then I realized the relpaths found in the output for -r and -c specified reqsins are only relative to the target reqsin file (probably simply copied from the target reqsin), regardless of the location of the output file.

  1. Should any setup.py annotations list relpath (if not adjacent) to the output file? I think so.
  2. Should -r and -c relpaths be calculated and annotated based on output location rather than input location? I would think so.
  3. If 2, then should that be part if this PR, which can be taken as an "annotation consistency" attempt?
  4. If 2 and/or 1 and output file is set to stdout, relpath should be anchored to the user's working dir, yes?

@graingert
Copy link
Member

graingert commented Feb 11, 2020

sometimes in a .in file I'll refer to another .txt file eg:

# requirements.in
ham
spam
# requirements-test.in
-r ./requirements.txt
pytest-anyio

What would the outputs look like?

@AndydeCleyre
Copy link
Contributor Author

@graingert

With the current state of this PR:

# requirements.in
requests
structlog
# requirements-test.in
-r ./requirements.txt
pytest

If no txts exist yet, pip-compile requirements-test.in will fail. Otherwise:

$ pip-compile --no-header requirements.in
certifi==2019.11.28       # via requests
chardet==3.0.4            # via requests
idna==2.8                 # via requests
requests==2.22.0          # via -r requirements.in (line 2)
six==1.14.0               # via structlog
structlog==20.1.0         # via -r requirements.in (line 3)
urllib3==1.25.8           # via requests
$ pip-compile --no-header requirements-test.in
attrs==19.3.0             # via pytest
certifi==2019.11.28       # via -r ./requirements.txt (line 1), requests
chardet==3.0.4            # via -r ./requirements.txt (line 2), requests
idna==2.8                 # via -r ./requirements.txt (line 3), requests
more-itertools==8.2.0     # via pytest
packaging==20.1           # via pytest
pluggy==0.13.1            # via pytest
py==1.8.1                 # via pytest
pyparsing==2.4.6          # via packaging
pytest==5.3.5             # via -r requirements-test.in (line 3)
requests==2.22.0          # via -r ./requirements.txt (line 4)
six==1.14.0               # via -r ./requirements.txt (line 5), packaging, structlog
structlog==20.1.0         # via -r ./requirements.txt (line 6)
urllib3==1.25.8           # via -r ./requirements.txt (line 7), requests
wcwidth==0.1.8            # via pytest

Note that if the files are adjacent, including ./ in the .in file is unnecessary, and if omitted will also be omitted from the output.

Answers to my "new questions" above would change the relpaths in the output in the case where --output-file is used to put it in some other folder.

@graingert
Copy link
Member

Looks very nice

@AndydeCleyre
Copy link
Contributor Author

@graingert Do you have a clear preference for either the setup.py annotation format, or if the annotation relpaths should all be based on the output location rather than the input location?

@graingert
Copy link
Member

graingert commented Feb 11, 2020

I think the annotation paths should be based on output location, I don't use setup.py maybe you could use file://../path/to/project/dir

@AndydeCleyre
Copy link
Contributor Author

I'm noticing the path handling is a little sloppy here:

$ mkdir some.folder
$ echo 'requests' > some.folder/reqsin
$ pip-compile --no-header some.folder/reqsin
$ ls some.folder
reqsin
$ cat some.txt
certifi==2019.11.28       # via requests
chardet==3.0.4            # via requests
idna==2.8                 # via requests
requests==2.22.0          # via -r some.folder/reqsin (line 1)
urllib3==1.25.8           # via requests

@graingert
Copy link
Member

graingert commented Feb 11, 2020 via email

@atugushev
Copy link
Member

@atugushev Should I open a discussion issue for general path handling improvements, covering at least annotations and compilation output path determination?

Please go for it.

Copy link
Member

@atugushev atugushev left a comment

Choose a reason for hiding this comment

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

Awesome work! 🎉 🎉 🎉

piptools/scripts/compile.py Outdated Show resolved Hide resolved
@graingert
Copy link
Member

@atugushev any chance for a release soon with this new feature?

@AndydeCleyre
Copy link
Contributor Author

I thought I'd done a final squash already, but I hadn't, so here it is. No code changes since the str.format change up here.

@atugushev
Copy link
Member

@AndydeCleyre could you please fix the QA issues?

…y and VCS reqs; fixes jazzband#881 and jazzband#293

Change Notes
------------

- Annotations now include reqsin/setup.py/stdin info when relevant
- Always send reverse_dependencies to writer._format_requirement as a dict, even if empty
- Invert checks for primary requirements not getting annotations, as they now do
- Bypass annotation noise in tests which don't target annotations anyway, by passing --no-annotate
- ireqs returned by get_best_match now have the parameter ireq's _source_ireqs, if any
- reverse_dependencies are no longer passed around independently outside the
  resolver/cache, but rather determined from an ireq's comes_from and _source_ireqs
- primary_packages are no longer passed around independently, but rather determined by
  an ireq's constraint
- In tests, reverse dependencies for fake ireqs are now specified by setting the ireq's
  comes_from
- These changes do _not_ improve handling of relative paths to reqsin files in annotations (see jazzband#1067)

Co-authored-by: Albert Tugushev <albert@tugushev.ru>
@AndydeCleyre
Copy link
Contributor Author

@atugushev whoops, didn't run tox after using the github interface to merge in the format string. Should be good when the tests finish now.

@atugushev
Copy link
Member

@AndydeCleyre thanks!

@graingert I'll try to release within 48 hrs.

@atugushev atugushev merged commit 3673c3c into jazzband:master Feb 20, 2020
@atugushev atugushev added this to the 4.5.0 milestone Feb 20, 2020
@atugushev
Copy link
Member

pip-tools v4.5.0 is released.

@gsakkis
Copy link

gsakkis commented Feb 21, 2020

OK, still looking for feedback on whether better path handling should be in a later PR, and on the current output:

$ echo -e 'requests\nstructlog' > requirements.in
$ pip-compile --no-header - <requirements.in -o requirements.txt
certifi==2019.11.28       # via requests
chardet==3.0.4            # via requests
idna==2.8                 # via requests
requests==2.22.0          # via -r -
six==1.14.0               # via structlog
structlog==20.1.0         # via -r -
urllib3==1.25.8           # via requests
$ pip-compile --no-header setup.py
click==7.0                # via pip-tools (setup.py)
six==1.14.0               # via pip-tools (setup.py)

If that output looks good to everyone, I'll get it covered by a test. Note that no line numbers are specified -- are they desired? It's not like a user will be able to cross reference it with a real file.

Lines numbers apparently were included in v4.5.0. FWIW I just run it over a large requirement set and as @graingert commented the diff noise is excessive even for small changes in requirements.in.

@jmbowman
Copy link
Contributor

I agree the diff noise is irritating, but the line numbers did just prove useful also:

mock==3.0.5               # via -c requirements/edx/../constraints.txt (line 70), -c requirements/edx/../constraints.txt (line 76), -r requirements/edx/paver.in (line 17)

Our constraints file had duplicate restrictions on the mock version added by separate developers in different PRs, had already gone 2 weeks without anybody noticing until this change highlighted it.

@AndydeCleyre
Copy link
Contributor Author

@gsakkis

My comment about there being no line numbers was only about annotations for stdin and setup.py -- are you getting line numbers for those?

@graingert
Copy link
Member

@jmbowman

I agree the diff noise is irritating, but the line numbers did just prove useful also:

mock==3.0.5               # via -c requirements/edx/../constraints.txt (line 70), -c requirements/edx/../constraints.txt (line 76), -r requirements/edx/paver.in (line 17)

Our constraints file had duplicate restrictions on the mock version added by separate developers in different PRs, had already gone 2 weeks without anybody noticing until this change highlighted it.

Isn't this illegal in a requirements.txt? This should throw an error

@graingert
Copy link
Member

I think there should be no line numbers in the generated requirements.txt, and duplicate constraints in a requirements.in considered an error (as it is in pip (I think))

@AndydeCleyre
Copy link
Contributor Author

@graingert

Would you open a new issue for discussion?

@jmbowman
Copy link
Contributor

The duplicates were in a constraints file pulled in via -c constraints.txt, which pip (or at least pip-compile) is apparently fine with.

@timmc-edx
Copy link

FYI: @adamchainz has opened an issue to discuss this: #1074 (includes a PR)

palango pushed a commit to raiden-network/raiden that referenced this pull request Mar 16, 2020
hackaugusto pushed a commit to raiden-network/raiden that referenced this pull request May 29, 2020
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement Improvements to functionality
Projects
None yet
Development

Successfully merging this pull request may close these issues.

show via for direct deps via not shown for packages installed from VCS
6 participants