Skip to content

Add --script option to uv init#7404

Closed
jbvsmo wants to merge 6 commits intoastral-sh:mainfrom
jbvsmo:feat/uv-init-script
Closed

Add --script option to uv init#7404
jbvsmo wants to merge 6 commits intoastral-sh:mainfrom
jbvsmo:feat/uv-init-script

Conversation

@jbvsmo
Copy link
Copy Markdown
Contributor

@jbvsmo jbvsmo commented Sep 15, 2024

Summary

This is a partial implementation of #7402
Since there was no discussion yet, I didn't get much further than adding the command line option and writing the .py file

Feel free to use this PR (or not). I am unsure if I will have time to finish it.

There is one opinionated bit which is the readme "type" which is part of the pep 723 spec but you may want to think about it before introducing any such thing

Missing:

  • The PackageName converts dots into dashes. I did not check how to fix it. You will see it when you run the command as the hello will show foo-py instead of foo.py
  • Add to the docs to teach people how to create scripts here
  • Combine python_request and requires_python as there is no standalone .python-version file
  • possibly add a shebang and make the file executable
  • Test cases

Test Plan

uv init --script foo.py

@jbvsmo jbvsmo marked this pull request as draft September 15, 2024 02:58
@zanieb
Copy link
Copy Markdown
Member

zanieb commented Sep 15, 2024

Sweet! Can you add a test case to crates/uv/tests/init.rs so we can see what the output looks like and iterate from there?

@jbvsmo
Copy link
Copy Markdown
Contributor Author

jbvsmo commented Sep 15, 2024

Sweet! Can you add a test case to crates/uv/tests/init.rs so we can see what the output looks like and iterate from there?

Sure, I did that. Observe how the cli parses the file name into the package converting something.py into something-py instead of just "something". But that is also something you guys might want to decide.

A file without extension is also supported, which is common for executable scripts with a shebang.

Comment on lines +493 to +495
# name = "myapp-py"
# version = "0.1.0"
# description = "Add your description here"
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

could avoid these lines?
I think most script authors won't go versioning the scripts, and they prefer to use module-docstring instead of description. the name field also inferred from filename usually.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

These are the kinds of opinion based decisions that are better chosen by the uv team, so feel free to perform them. I just wanted to kickstart the feature. I copied the same defaults from the --app solution.

IMO though, versioning scripts is quite common and encouraged in the literature.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

IMO, it'd be good to have minimal header metadata (just like PEP 723's mentioned example).

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I would definitely omit the name here, I think.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

(Seems like the easiest way to deal with your package name problems too)

Copy link
Copy Markdown
Contributor Author

@jbvsmo jbvsmo Sep 16, 2024

Choose a reason for hiding this comment

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

Ok, that is really good advice @zanieb . I believe I was complicating things trying to change how package names are parsed when in this case there is really no package
I pushed a commit with these changes and also @T-256 requests

----- stdout -----

----- stderr -----
Initialized project `myapp-py` at `[TEMP_DIR]/foo/myapp.py`
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

could strip file extension when set the project name?

Copy link
Copy Markdown
Contributor Author

@jbvsmo jbvsmo Sep 15, 2024

Choose a reason for hiding this comment

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

Yes, I would like so. I was afraid to break something regarding parsing package names since it is done automatically at the cli step

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I'd special case this entirely to say something like "Initialized script at {path}"

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Done too

Comment on lines +487 to +490
# /// readme
# You can execute this file with any tool compliant with inline script metadata. E.g.:
# $ uv run myapp-py
# ///
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Can you also add no-readme tests to prevent extra newlines?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Done

- path handling
- script name based on path
- fix edge cases regarding no path or app name
- test cases
@jbvsmo
Copy link
Copy Markdown
Contributor Author

jbvsmo commented Sep 16, 2024

I think the only thing really missing now after the changes above are the docs update to tell people how to create scripts.

I decided against a shebang because posix does not really support multiple arguments and "#!/usr/bin/env uv run" would fail in some systems.

@jbvsmo jbvsmo marked this pull request as ready for review September 16, 2024 00:53
if path.join("pyproject.toml").exists() {
if project_kind == InitProjectKind::Script {
if explicit_path.is_none() {
anyhow::bail!("Missing script name to initialize",);
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Suggested change
anyhow::bail!("Missing script name to initialize",);
anyhow::bail!("Missing script name to initialize");

if explicit_path.is_none() {
anyhow::bail!("Missing script name to initialize",);
}
if path.exists() {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Should we use try_exists here?

Should we add metadata to the front of the script if it doesn't exist? Or should we tackle that in a separate pull request? I think we already have machinery for this for uv add

}
if path.exists() {
anyhow::bail!(
"Script is already initialized in `{}`",
Copy link
Copy Markdown
Member

@zanieb zanieb Sep 16, 2024

Choose a reason for hiding this comment

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

Suggested change
"Script is already initialized in `{}`",
"Script already exists at `{}`",


// Create the `README.md` if it does not already exist.
if !no_readme {
if !no_readme && project_kind != InitProjectKind::Script {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Generally, I'd use matches! instead of != here. I'm not sure why. cc @BurntSushi

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

matches! is strictly more flexible since you don't need a PartialEq impl to have it work. But for this specific instance, I don't think there's any meaningful advantage to one over the other. I don't mind the != here personally, especially if you aren't adding PartialEq impls just to make it work.

writeln!(printer.stderr(), "Initialized project `{}`", name.cyan())?;
}
// Initialized script
Some(path) if project_kind == InitProjectKind::Script => {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Perhaps we should have a match on project_kind above this? If we change something around path handling in the future, we could easily miss the Script case with this structure.

// Make sure a project does not already exist in the given directory.
if path.join("pyproject.toml").exists() {
if project_kind == InitProjectKind::Script {
if explicit_path.is_none() {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Best practice is to do something like let Some(path) = explicit_path else { bail }. However, similar to https://github.com/astral-sh/uv/pull/7404/files#r1761072383, you may want to just match on project_kind above on L44 when determining the path variable.

@zanieb
Copy link
Copy Markdown
Member

zanieb commented Sep 16, 2024

Thank you!

Lots of small comments on the implementation. If you would rather we just finished, let us know.

@zanieb zanieb self-assigned this Sep 16, 2024
@zanieb zanieb added the cli Related to the command line interface label Sep 16, 2024
@jbvsmo
Copy link
Copy Markdown
Contributor Author

jbvsmo commented Sep 16, 2024

@zanieb sure, go ahead thank you!

@charliermarsh
Copy link
Copy Markdown
Member

Merged as #7565. I added @jbvsmo as a co-author on that change, hope that's ok.

@jbvsmo jbvsmo deleted the feat/uv-init-script branch September 25, 2024 22:51
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

cli Related to the command line interface

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants