Repository navigation
uv init: Project layouts, defaults and terminology #8178
Description
Activity
I think
uv init --layout=<bare|none>is very useful for me when it comes to creating a Django app. I am searching for a way to initialize the project with only apyproject.toml, so that I can usedjango-adminto manage my project without too much unnecessary stuff.Reacted by Michael Hart, William Henney, Andy Beaumont, Farzin and John Cheng- addedneeds-decisionUndecided if this should be doneUndecided if this should be done
on Oct 24, 2024 Thanks for your thoughts! I'll own responding to this and seeing what we can integrate.
Reacted by Michael Hart, my1e5 and Steven KesslerI've only ended up in this thread because I was confused about the default behaviour here - to me it feels like
uv initwith no options should do the equivalent ofuv init --layout=<bare|none>mentioned above and create nothing but apyproject.toml. I can't imagine ever needing uv to create the.gitignore,.git/andhello.pyso for that to be the default seems kinda wild.Feel free to ignore me though, I realise I'm a data point of one and maybe those do somehow make sense to other devs.
Reacted by Frank Hoffsümmer, the-vampiire, matthias-busch, Oliver Bestwalter and Ian CarrollI think both the options available and their documentation are somewhat confusing at present. There are multiple different decisions in determining how a project should be structured, but those decisions seem to be split between
uv init's sub-optimal presets and further configuration options inpyproject.tomlthat need to be set manually.Often, I might want to create a standalone application, such as a Flask webapp. This doesn't need to expose a Python API or be imported into other projects, so I would assume I don't need a build system to turn my code into a package. I'll probably just write a Dockerfile that will produce a container image with my dependencies installed, ready to run my code directly.
In terms of layout, I probably want a 'flat layout', where my code is in
./myapp. This is what Flask, for instance, recommends. That's not available fromuv init-- I have to choose between:- a 'Packaged Application'
This configures a build system I don't need and nests all my code in./src/myapp. - a 'Library'
This has all the downsides of a 'Packaged Application', and also doesn't specify a command-line entry point. - an 'Application'
This puts my code in., the project root, which will get cluttered quickly if I want to build anything bigger than a single-file script.
None of these do what I want! No matter what I choose, I'll have to move things around and change
pyproject.tomlto account for the changes.This is addressed in #6460, which seems to be resolved by #6585, adding 'Virtual Projects', which don't get built or installed as packages. But the only place this is documented is under the setting that enables it, where I can read a brief summary of what a virtual project is, but not why I might want to use one. #6585 states that a project will be treated as 'virtual' merely by the lack of a
[build-system]inpyproject.toml, but this isn't documented there. The documentation onuv initdoesn't mention 'virtual projects' at all.A brief aside regarding 'virtual projects' and
uv init: it seems likeuv init --virtualcreates a virtual project, but the--virtualflag can't be used with--package(for a 'Packaged Application') or with--lib(for a 'Library'). Anduv init --virtualproduces exactly the same result asuv initwithout the--virtualflag, not even addingpackage = falsetopyproject.toml. So it's not clear to me what the flag is supposed to do.I love
uvas a whole, but this is extremely confusing to me. Maybe I'm mistaken on some of this, and I'm doing something wrong, in which case please feel free to correct me.Reacted by Andy Beaumont, Nathan McDougall , William Henney, the-vampiire, Ivan Kleshnin, Aleksandr Aksarin, acottuli, Maximilian Orsley and Clemens Reiffurth- a 'Packaged Application'
I've only ended up in this thread because I was confused about the default behaviour here - to me it feels like
uv initwith no options should do the equivalent ofuv init --layout=<bare|none>mentioned above and create nothing but apyproject.toml. I can't imagine ever needing uv to create the.gitignore,.git/andhello.pyso for that to be the default seems kinda wild.I can follow the logic for not wanting a "hello.py", but I can fully understand that an example (in the terms mentioned by the request) is a good thing for most of the people. I cannot support not creating the ".git" and ".gitignore" files. All amateurs (which I count myself) should have it, but many do not know about it. Nothing serious should be done without them.
What we need is a revamp on the "layout" structure. Which is confusing. I had to test all the options to understant what is meant. With the "--layout" option is clear and does what people do. .
Reacted by Michael Hart, theo court, matthias-busch and Aleksandr AksarinReacted by Marcelo HuertaI can't imagine ever needing uv to create the .gitignore, .git/ and hello.py so for that to be the default seems kinda wild.
Counterpoint: I can't imagine ever needing to start a python project without a
.gitignorefile, and it's annoying to go and fetch the exact same file every time, when uv aims to be a one-stop shop. Of course, a minority of people don't use git for version control, although a host of other dev tools respect .gitignore when it comes to ignoring files to search/ index/ include so arguably it's useful to have even if you're not using git.I think that
hello.py, while deleted/ renamed immediately by developers who start new projects regularly, acts as a useful marker for where your functional code should go. Between script, app, and library projects, flat or src layouts, multi-package projects etc., that may not be clear to someone less experienced, or even an experienced developer who hasn't followed the breakneck pace of the evolution of python's best practices in the last few years.Reacted by my1e5, Vikram Saran, bess and Aamir Jawaid@clbarnes Not everyone is starting a new project, nor running uv where their .gitignore, .git repo or README lives - think mono-repos, projects where uv is inside docker, projects where you're in a git submodule etc. - in some of these cases, creating a .git repo is a positively hostile thing to do as you can suddenly end up with nested repositories being pushed and then someone (me) has to untangle it.
Reacted by 93578237, Frank Hoffsümmer, Tim Van Wassenhove, the-vampiire, bess and Andrés Ferreiro GonzálezLots of insightful thoughts. I would also argue for project layout to be separated from rather the project is intended to be an application or a python package. In fact, it is not uncommon for python applications to also use a src layout. This is especially useful with containers as mounting all source code files is just a singular mount, or for standardized use in CI/CD.
Reacted by Andy Beaumont, uwu-420, Carlos Eduardo Ferreyra and Rasmus ScholerI second the idea of a more customized option for
uv init. I would also like the option to setup a personal default configuration.Additionally, it would be neat to be able to customize the starting
.gitignore(at least, setting up a personal default.gitignore).Typically, I would systematically exclude
.*_cache(.pytest_cacheand.ruff_cache) and.venv, and I'm pretty sure developers' needs would change based on their favorite tools.Also, while I don't disagree with @clbarnes 's justification of
hello.py(making me neutral about the creation ofhello.py), I thinksrc/<…>/__init__.pyshould not be initialized with code inside it. My big issue with this behavior is that this dummy code not visible at first sight when looking at the project. Worse than having to clean it up, devs might miss it,Reacted by Andy Beaumont, my1e5, Chris Barnes, matthias-busch, Vikram Saran and bess- added a commit that references this issue
on Feb 5, 2025 Also coming here because I love the
uvexperience overall... except for its package layout creation options, which I found really confusing. I wanted to create a src-layout for a library that was going to be packaged and released eventually, and by far most of my time struggling withuvwas spent trying to figure out how to get that config.The
--library, etc. options are not great currently, because they bundle too many things together. It's totally fine to have defaults for new users (and please do that). But if I want a src-layout for my project (whatever it is), it would be so much easier to just ask explicitly ("better than implicit") for it by tacking on a--layout=srcoption to myuv initinvocation.Also, I was kind of expecting this choice of layout to be also reflected in the section for
uvof thepyproject.tomlfile, at least, so alluvinvocations know about it. Right now, I have a makefile whose main function is to tack onuv run --project srcto all the commands I need to run during development.For me, this is the only thing making me hesitate to recommend
uvmore broadly to my team.2 remaining items
How about allowing users to provide their own configuration template in some uv config directory? Or even several templates one could choose with something like
uv init --template=my-named-preset?@hunter-gatherer8 please see #9754
Reacted by hunter-gatherer8Can someone take a little time to elaborate the reasoning behind "src" layout for non-Pythonistas 🙏
With "flat" layout:
project_name/ project_name/we already have a folder-level isolation. It does look "nested" to a TypeScript-first version of me 😄 I get that the project can have multiple packages and those packages will, supposedly, have common files under
project_name/src. But is there any benefit for simple (and probably more frequent) one-package-per-project cases?Quoting the article, that everyone refers to:
https://packaging.python.org/en/latest/discussions/src-layout-vs-flat-layout/The src layout requires installation of the project to be able to run its code, and the flat layout does not.
I don't get this point. With Poetry I had to install my library package, and it definitely has the "flat" layout.
The src layout helps prevent accidental usage of the in-development copy of the code.
This point is more clear. But it's a "protection by convention", not the strongest argument. We can still accidentally put a non-public data or code in
src. So, again, I would like to learn pros/cons of each layout better, maybe someone can suggest another article or video?Can someone take a little time to elaborate the reasoning behind "src" layout for non-Pythonistas 🙏
import package_namemay pick up the code from your source checkout and not the installed one depending on your current working directory. src prevents thishttps://hynek.me/articles/testing-packaging/ and https://blog.ionelmc.ro/2014/05/25/python-packaging/#the-structure have some discussion of the topic.
Reacted by Juan Luis Cano Rodríguez and matthias-buschCan someone take a little time to elaborate the reasoning behind "src" layout for non-Pythonistas 🙏
Python was originally designed as a scripting language, more akin to bash or perl than Java. As such, it's pretty liberal about sweeping local directories for python code to import and execute. However, there are a few different ways of executing python code and that module discovery happens differently in different execution contexts. Some tools designed with that style in mind (e.g. testing utilities) also do things under the hood to make things "easier", which actually makes module discovery even more complicated.
As python has been used for bigger, more complicated projects, this module discovery complexity has been found to add little utility and a lot of headaches. Isolating your code in a
srcfolder prevents it from being auto-discovered in most cases, which improves consistency, puts the developer in control of what is imported and how, and makes your development environment behave more similarly to your deployment environment.Python as a language has changed to support bigger, more complicated projects (e.g.
typing), as has the tooling around it (e.g.uv!).Also it helps separate the actual module from general project-related cruft like CI configuration, scripts, infrastructure setup, docs etc..
Reacted by Andy Beaumont, Ivan Kleshnin, Marcin Kowalski, Alex Hudspith and Davis BennettComing from using
poetryI also find theuv initoptions a bit confusing and convoluted.I personally would prefer the pretty simple/bareback approach of poetry's
poetry new some-namefor a project skeleton that looks like this:❯ tree bar-delete-later bar-delete-later ├── README.md ├── bar_delete_later │ └── __init__.py ├── pyproject.toml └── tests └── __init__.pyand
poetry new --src some-nameresulting in:❯ tree foobar-delete-later foobar-delete-later ├── README.md ├── pyproject.toml ├── src │ └── foobar_delete_later │ └── __init__.py └── tests └── __init__.pyThis might be a simple case of personal preference and I totally (and welcome) uv's intent to being as user-friendly as possible but I personally think pruning back on the options and just giving options for the src/flat layout would suffice and more straightforward.
I would also love to have the
testsfolder generated by default for both approaches.Reacted by bessI am grateful that this conversation is happening here. As someone who recently needed to setup a Python project, I ran into the flat vs src decision myself. In the end, I decided that I would use the src layout as my default, as well as making the choice to use
uvas my preferred tool. But the src-vs-flat choice in Python will probably never go away and I think it won't be easy for uv to, for the lack of a better phrase, "make the right choice." There won't be a perfect answer.Having said that, I think simply discussing this matter in the uv documentation will go a long way to helping the community, especially people new to Python project layouts like me. Today, the uv documentation (https://docs.astral.sh/uv/concepts/projects/init/) discusses the difference between "Application", "Packaged Application", and "Library". This page could be a good place to discuss why the uv team chose Application as the default and what the implications are (e.g., perhaps referencing https://hynek.me/articles/testing-packaging/).
This is akin to the practice of keeping architectural decision records (https://adr.github.io/). The goal is not to justify that the current decision to make Application the default is the right one--people will always debate such a decision. The goal is to give users context about how the decision was made at the time, which empowers users to better evaluate the alternatives given their own situations and future evolutions in the Python community.
Thanks again for considering this topic. And many thanks to @MikeHart85 for his thoughtful and intelligent proposal.
Reacted by bess and buster-blueThis page could be a good place to discuss why the uv team chose Application as the default and what the implications are (e.g., perhaps referencing https://hynek.me/articles/testing-packaging/).
I think this is summarized in #3957 (comment) — we don't want this to be the default longterm.
We intend to revisit this entire interface once our build backend is ready.
Reacted by John Cheng and buster-blueThank you for pointing this proposal!
I've been following this thread, but I'm a noob in the open source community, so a little shy to give any opinion.Proposal
following with @zanieb ideas and the philosophy of uv, for becoming the cargo of python (or npm/bunof python):
Having uv as a tool for scaffolding an initial template is a must.What it goes in my mind, is that you can set a basic "PEP compliant version of project initialization" for
uv init(specially when it comes matching pyproject specs., and useuvxfor anything that has to do with custom templates (including those that work with cookiecutter or copier. This follows 3 goals:- keeps a clean workflow for the uv client, knowing that creating a project is a one time action during the lifetime of the project itself.
- Maintain strong compliance with PEP without sacrificing out-of-the-box features from uv.
- Make a equivalent use of what it was
npx create-react-app|npx create-expo-appand would beuvx create-{some-python-template}
Also, you could use the proposal as a way to migrate from current out the box options out there in the python community, to a more uv compliant version of it. Benefits:
- Decrease of python community friction, when migrating to the uv ecosystem.
- Leave
uvxas an entrypoint for extended scaffolding models (i.e, even one for cloud-based or AI projects), and even using the community popular choices as a possible builtin-template for uv.
An example of this was the
uvx migrate-to-uvscript which allows to migrate from poetry to uv, but this very concept could also be applied for scaffolding templates from scratch.Possible Drawbacks
- Dealing with a new set of standards when designing a new template through
uvxand have the community to adhere to those standards - Increased complexity in maintaining two separate but related commands (
uv initanduvx). - Potential confusion for new users about when to use
uv initversusuvx. - The need for significant effort to define and maintain the "PEP compliant" template for uv init, ensuring it remains up-to-date with evolving Python standards.
- Reliance on the community to create and maintain high-quality templates for
uvx. If the community uptake is slow, the uvx ecosystem might lack useful options. - Overlap in functionality with existing tools like
cookiecutterorcopiermight lead to fragmentation or resistance from users already comfortable with those tools.
Basic Structure
The
uv initpurpose should be to set up the least amount of settings for the project, since even when you need scaffolding, you also need to keep the option for a full customization from scratch. having said that, I agree with the initial proposal of the issue, but with a small caveat: all templates, no matter if flat,lib, or package should have the/srcfolder, since its a good widely accepted standard for the dev community, and also separates the project configuration from any project implementation (even for frontend devs).Easter Egg
the team could even create a dedicated package for using the
uvxclient, to build templates (uvx start-project --with cookiecutter|copier|[uv])... and those templates can be selected by the uv team to comply with the uv ecosystem.Reacted by bess, Uno Yakshi, Awan and Aamir Jawaiduv is a great tool for managing virtual environments, even outside of packages, so I'm wondering: should there be a template for e.g.
--venv, which just gives you all you need to start managing a virtual environment?.git .gitignore pyproject.toml # (with no [build-system]) .python-versionCurrently,
uv init --no-readme --no-packagegets you close to this but creates an undesired "main.py", anduv init --bareis good but misses .python-version, and the git files. So - potentially a--no-mainoption, or allowing--vcs gitetc to work with--barewould achieve this - but perhaps this use-case is worthy of its own template?We started using a monorepo structure and we'd still like to have "apps" which do not need a build system but now we have many and the flat layout used by
--appleads to import conflicts. We'd like to have apps with a src directory, but I could not find an option to initialize this via uv.It feels like
uv initis trying to hard to think for the user and does a lot of presets/magic. Adding a--layoutoption to give the user the flexibility to choose the project structure themself would be great.
In addition a--build-backend noneoption could also help to disable adding a build-backend.Reacted by J Rob Gant, Marcelo Huerta and Alexander Nigl+1 on the whole discussion here. It'd be great if uv could support templates out of the box especially. We use cookiecutter in our project (an SDK), but telling our customers to use cookiecutter for starter templates seems ugly.
Another idea is, if uv doesn't want to support this OOB, if there was a plugin system which would allow hooks like cookiecutter etc to be developed as plugins, then we could ask folks that they need to install uv with cookiecutter plugin, and then those systems could work without being included officially in uv core.
EDIT: I see that #8178 (comment) this comment has a great suggestion using uvx!
Request for feedback: We're changing the default layout to "Application package". Please share your feedback and questions in #19906.
I've included a summary below.
We're changing the default layout when creating a project with
uv init. In the future, projects will have a build backend by default, and store the source code in a directory. This layout supports some additional features that the flat layout doesn't support.So we go from this:
. ├── main.py ├── pyproject.toml └── README.mdto this:
. ├── pyproject.toml ├── README.md └── src └── foo └── __init__.pyPackaged projects can do a number of things that the unpackaged, flat layout without build backend can't do: The project can be imported from anywhere (
import foo), it can be distributed (uv publish), it can be installed as dependency (uv add path/to/foo_projectoruv pip install -e path/to/foo_project) and it can have entrypoints (uv run fooor justfoowith an active venv).A packaged layout is also required for most testing frameworks:
. ├── pyproject.toml ├── README.md ├── src │ └── foo │ └── __init__.py └── tests └── test_foo.pyYou can try the new behavior with
uv init --preview-features package-by-default, which makes the behavior ofuv init --packagethe default. To go back to the old behavior, you can always useuv init --no-package.There are still many projects for which the flat, unpackaged layout is the right choice, and this layout won't go away. Instead, we're changing the default to include a build backend and the nested directory layout a build backend requires, and making the non-packaged layout the option.
A preview of the updated documentation is available in #19026.
This thread is for feedback and questions on this change ahead of stabilization.
Reacted by Clément Robert, Chris Barnes, Davis Bennett, Josh Moore, Juan Luis Cano Rodríguez, roteiro, Christian Michelsen, Aamir Jawaid, Ajay Mehta and shimizureiReacted by Juan Luis Cano Rodríguez and Michael HartI coded mini-sript to fix this problem (you can add to PATH and use as
uv_init), its customizible:https://gist.github.com/iamlostshe/b1a775786da7050630ceb56ec8e75660
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.