-
Notifications
You must be signed in to change notification settings - Fork 900
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
Initial implementation of uv add
and uv remove
#4193
Conversation
let upgrade = Upgrade::default(); | ||
let extras = ExtrasSpecification::default(); | ||
let exclude_newer = None; | ||
let dev = false; // We only add regular dependencies currently. |
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'm not sure if this is correct? Should dev dependencies ever be synced even if they weren't modified?
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.
It's possible that the dev dependencies could change by adding a non-dev dependency, because they're all locked together. (E.g., maybe the dep you add has a transitive dep on one of the dev deps, so now it's more constrained.) So, I guess we do have the include them? But it's not really right to sync them unconditionally. The same problem exists with extras. We shouldn't be syncing them unconditionally. We need to know which extras the user wants activated.
I'm wondering if add
should imply lock
, but not sync
? Since locking is much simpler: we lock everything.
(I think there is a general problem here that exists as long as add
implies lock
or sync
. (Rye has this problem too). Any arguments that are typically passed to lock
or sync
to guide resolution or installation now need to be passed to add
too... So, e.g., we might need exclude-newer
too.)
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.
Made add
and remove
do an unconditional full sync for now.
let mut pyproject = PyProjectTomlMut::from_toml(project.current_project().pyproject_toml())?; | ||
for req in requirements { | ||
let req = Requirement::from(LenientRequirement::from_str(&req)?); | ||
if pyproject.remove_dependency(&req)?.is_none() { |
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 happens if you uv remove anyio
, without including the specifier?
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.
remove_dependency
removes by package name, so that works.
|
||
let mut pyproject = PyProjectTomlMut::from_toml(project.current_project().pyproject_toml())?; | ||
for req in requirements { | ||
let req = Requirement::from(LenientRequirement::from_str(&req)?); |
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 we should enumerate some of the TODOs that aren't tackled yet. For example:
- We should accept unnamed URLs, and resolve them to named requirements.
- We need to figure out how this interacts with
tool.uv.sources
. When do we add data totool.uv.sources
rather thanproject.dependencies
? \cc @konstin, you two should discuss this. - Editable requirements?
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.
Tracking in #3959.
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 we should just use pep508_rs::Requirement::from_str
instead of LenientRequirement
... We are a little inconsistent about it, but in general, LenientRequirement
is really intended for third-party metadata over which the user has no control. We shouldn't let them add invalid requirements here.
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 also applies to the modification code in crates/uv-distribution/src/pyproject_mut.rs
. What do you think?
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.
That makes sense to me, I removed the use of LenientRequirement
.
crates/uv/src/cli.rs
Outdated
pub(crate) struct RemoveArgs { | ||
/// The packages to remove as PEP 508 requirement strings. e.g. 'flask==2.2.3' | ||
#[arg(required = true)] | ||
pub(crate) requirements: Vec<String>, |
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 wonder if this should be Requirement
as long as we're parsing these directly. But, I think what you have here is probably better.
let mut to_remove = None; | ||
for (i, dep) in deps.iter().enumerate() { | ||
if let Some(dep) = dep.as_str().and_then(|dep| parse_requirement(dep).ok()) { | ||
if dep.name.as_ref().eq_ignore_ascii_case(req.name.as_ref()) { |
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 if a dep is included multiple times? E.g.:
dependencies = [
"flask > 2 ; python_version >= '3.6'",
"flask < 2 ; python_version < '3.6'",
]
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.
Hmm it would only remove the first occurrence then. I think Rye has this bug as well?
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.
Updated to remove all occurrences. Also changed uv remove
to take package names, not full requirements, because otherwise uv remove flask>2
becomes more complicated. I think package names make more sense anyways? Unless we want to support e.g. removing unnamed requirements by URL?
d934330
to
a955b35
Compare
|
||
match to_replace { | ||
Some(i) => { | ||
deps.replace(i, req.to_string()); |
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 should happen if there are multiple occurrences? My guess is we replace them all.
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.
Replace them all with the single new requirement? i.e. replace the first occurrence and remove the rest?
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.
Implemented it that way, I think that makes the most sense, although it might be good to warn/log because this whole scenario is a bit questionable.
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.
A few more comments. I am happy to re-review but also trust you to address them and merge when you think they're closed out.
Summary
Basic implementation of
uv add
anduv remove
that supports writing PEP508 requirements toproject.dependencies
.First step for #3959 and #3960.