-
Notifications
You must be signed in to change notification settings - Fork 2.7k
Description
Thank you for working on this wonderful tool.
I'd like to suggest several changes to uv init arguments and defaults, with the following goals:
- Use consistent and established terminology
- Options to support common usage patterns, so seasoned Python developers can have things their way
- Defaults which guide Python newcomers towards best practices, hint at available features
Much of this revolves around separating the concept of project directory structure ("layout" in Python) from project purpose and contents ("library", "application", etc), as well as disambiguating the overloaded term "package". Currently these terms are conflated and inconsistent in uv.
Apologies in advance for the massive wall of text.
Current behavior
"Application" (default):
$ uv init [--app] project-name
$ tree project-name/
project-name/
├── hello.py
├── pyproject.toml # only [project] (builds don't work due to misnamed hello.py)
└── README.md"Library":
$ uv init --lib project-name
project-name/
├── pyproject.toml # [project], [build-system]
├── README.md
└── src
└── project_name
├── __init__.py # contains "hello" code
└── py.typed"Application Package":
$ uv init [--app] --package project-name # --app appears to do nothing here, in spite of docs suggesting it
project-name/
├── pyproject.toml # [project], [project.scripts], [build-system]
├── README.md
└── src
└── project_name
└── __init__.py # contains "hello" codeSuggested behavior
Src layout (default):
$ uv init [--layout=src] project-name
project-name/
├── pyproject.toml # [project], [project.scripts], [build-system]
├── README.md
└── src
└── project_name
└── __init__.py # empty
└── example.py # def say_hello(): print("...")Flat layout:
$ uv init --layout=flat project-name
project-name/
├── pyproject.toml # [project], [project.scripts], [build-system]
├── README.md
└── project_name
└── __init__.py # empty
└── example.py # def say_hello(): print("...")Single module layout:
$ uv init --layout=single project-name
project-name/
├── pyproject.toml # [project], [project.scripts], [build-system]
├── README.md
└── project_name.py # def say_hello(): print("...")
# NOTE: file name MUST be project_name.py (it replaces the package)Bare / no layout:
$ uv init --layout=<bare|none> project-name # maybe pick one rather than allowing either one
project-name/
├── pyproject.toml # [project]
└── README.md- Add
--no-entrypointto suppress[project.scripts]- Consider adding
--entrypoint=nameto customize key name under[project.scripts], defaulting toproject-name
- Consider adding
- Add
--build-system=<hatchling|...|none>, withhatchlingbeing default, andnoneto suppress[build-system]- Consider adding
--no-build-system, synonymous with--build-system=none, for symmetry - Remove
--no-package
- Consider adding
- Add
--typedto create apy.typed - Remove
--package - Remove
--app, or:- Make synonymous with
--entrypoint=project-name - Consider generating example code to be more "app-like"
- Consider
__main__.py[1] instead ofexample.py, but that may unnecessarily confuse newcomers
- Make synonymous with
- Remove
--lib, or:- Make synonymous with
--no-entrypoint - Consider generating example code to be more "lib-like"
- Consider implying
--typed(IMO explicituv init --lib --typedwould be better)
- Make synonymous with
I would just remove --lib and --app to keep things simple.
Reasoning for suggestions
- "Entry point" is the established term for shortcuts to functions that ultimately become commands when installed [2]
- "Layout" is the established term for project directory structure [3], [4]
- Src layout should be default
- It is the most robust, avoids import conflicts, most suitable for serious projects
- Prevents needing to restructure when project outgrows other layouts
- Uv eliminates its main downside (very easy to get up-to-date REPL):
uv run python>>> import project_name
- Flat layout is also quite popular, and currently not available in
uv init - Single module (current
--app, default) layout is the most limited- It should most certainly not be the default
- Has issues and limitations which will confuse newcomers
- Should really only be used for single file projects, if at all
- Currently broken for builds due to example file always being
hello.py - Changing file to
project_name.pyallows builds to work
--layout=nonefor custom layouts, notebook projects, etc
- Src layout should be default
- Project layout is not related to whether it is an "app" or "lib"
- Any layout can be an application or a library
- Project contents and usage determine whether it is an application, or library, or both
- Not as clear cut in Python as "executable" vs "library" projects in compiled languages, shouldn't attempt to force that pattern since it doesn't fit
- "App" and "Application" terms should probably be reserved for possible integration with tools like PyInstaller, cxfreeze, etc (IE, generating a self-contained binary executable for distribution)
- A library isn't required to have PEP 561 compliant
py.typed
- Current use of
--packageconflates it with build-system/distribution, src layout, and absence ofpy.typed- A "package" in Python is simply a directory with an
__init__.py[5] - Ideally, the term "package" should not be used for anything else, to avoid confusion
- A package doesn't have to be distributable / have a build-system
- Single module (IE, non-package) distributions are a thing (hence
--layout=singlewith build-system)
- A "package" in Python is simply a directory with an
__init__.pyshould not contain general purpose code__init__.pyis a special purpose file, and putting arbitrary code there can easily have unintended side effects- Its meant for initializing packages, making symbols available at package level,
__all__, etc [6] - Putting the hello world example there can mislead newcomers into thinking this is where all their code belongs
- Don't see a reason not to include entry point and build system by default in all layouts which support it
- Can easily be removed / ignored / switched off if not needed
- Default presence shows newcomers what is possible / where it belongs
- Reduces friction when a project graduates from hobby to distribution
- Layouts are mutually exclusive, hence grouping them under one argument with a value
- Less room for confusion than having multiple differently named options, which may or may not be mixable
- All three layouts (src, flat, single) work with
uv run project-nameanduv buildas shown- Only
uv initneeds to change, to support generating them
- Only
Context
- Src layout used to be the default until: Add
--appand--liboptions touv init#6689 - "App", "lib" and "package" terminology introduced around here: Add support for virtual projects #6585
Discussion in the latter floated using "distribution" (ala PDM) instead of "package" (ala Poetry) in some contexts, but it seems "package" won out. I would argue "distribution" would have been the correct choice. As mentioned, "package" has a very specific meaning in Python, not strictly related to whether the project is built for distribution.
Thanks for considering.