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" code
Suggested 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-entrypoint to suppress [project.scripts]
- Consider adding
--entrypoint=name to customize key name under [project.scripts], defaulting to project-name
- Add
--build-system=<hatchling|...|none>, with hatchling being default, and none to suppress [build-system]
- Consider adding
--no-build-system, synonymous with --build-system=none, for symmetry
- Remove
--no-package
- Add
--typed to create a py.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 of example.py, but that may unnecessarily confuse newcomers
- Remove
--lib, or:
- Make synonymous with
--no-entrypoint
- Consider generating example code to be more "lib-like"
- Consider implying
--typed (IMO explicit uv init --lib --typed would be better)
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.py allows builds to work
--layout=none for custom layouts, notebook projects, etc
- 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
--package conflates it with build-system/distribution, src layout, and absence of py.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=single with build-system)
- A prominent example would be
six, which is a single module library: PyPI GitHub
__init__.py should not contain general purpose code
__init__.py is 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-name and uv build as shown
- Only
uv init needs to change, to support generating them
Context
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.
Thank you for working on this wonderful tool.
I'd like to suggest several changes to
uv initarguments and defaults, with the following goals: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":
"Application Package":
Suggested behavior
Src layout (default):
Flat layout:
Single module layout:
Bare / no layout:
--no-entrypointto suppress[project.scripts]--entrypoint=nameto customize key name under[project.scripts], defaulting toproject-name--build-system=<hatchling|...|none>, withhatchlingbeing default, andnoneto suppress[build-system]--no-build-system, synonymous with--build-system=none, for symmetry--no-package--typedto create apy.typed--package--app, or:--entrypoint=project-name__main__.py[1] instead ofexample.py, but that may unnecessarily confuse newcomers--lib, or:--no-entrypoint--typed(IMO explicituv init --lib --typedwould be better)I would just remove
--liband--appto keep things simple.Reasoning for suggestions
uv run python>>> import project_nameuv init--app, default) layout is the most limitedhello.pyproject_name.pyallows builds to work--layout=nonefor custom layouts, notebook projects, etcpy.typed--packageconflates it with build-system/distribution, src layout, and absence ofpy.typed__init__.py[5]--layout=singlewith build-system)six, which is a single module library: PyPI GitHub__init__.pyshould not contain general purpose code__init__.pyis a special purpose file, and putting arbitrary code there can easily have unintended side effects__all__, etc [6]uv run project-nameanduv buildas shownuv initneeds to change, to support generating themContext
--appand--liboptions touv init#6689Discussion 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.