Open woutervh opened 9 months ago
Thanks for engaging with the project!
Specifically, you would like us to read .env
files before every operation? Or something else?
Specifically, you would like us to read .env files before every operation? yes.
pip supports a config-file pip.ini / pip.conf all those values in that config-support can also be set as environment-variables
I have not yet seen a reference that uv pip
supports such config-file or these envvars,
but many times I set such project-specific pip-envvars, the same for twine.
some examples:
<project-dir>
.env
.venv/
docs/
src/
tests/
so my request is to support .env-files in $CWD, and optionally in parent-levels (e.g in just this is configurable)
I always try to setup projects in an isolated way:
<project-dir>
.env
.venv/
docs/
src/
tests/
var/cache
var/cache/ipython
var/cache/pytest
var/cache/python
var/cache/ruff
var/cache/sphinx
var/cache
var/log
var/tmp
to make ruff use the my .env to read RUFF_CACHE_DIR, I currently have to install ruff in the .venv and use python-entrypoint to execute ruff (slowing things down)
I use my package https://github.com/libranet/autoread-dotenv to read the .env via sitecustomize-hooks, so the env-vars become available for any python-generated entrypoint.
I'm going to mark this as not-planned for now. We may revisit in the future. If people have additional use-cases or thoughts please feel free to share!
I have a similar use case.
I am launching machine learning demos for students, and I am currently using Pipenv which loads environment variables from a file named .env
. That file contains variables like DO_NOT_TRACK=1
and HF_HUB_DISABLE_TELEMETRY=1
which are applied for all the commands I run via pipenv run
.
As a workaround, I can define them in my shell profile, but it would be great to have it natively supported by uv.
I am also migrating from pipenv which uses .env
out of the box. It would be great if uv run
could support this.
Willing to consider this further, i.e., we'd read it in uv run
only.
This might of inspiration, PDM supports something like this: https://pdm-project.org/en/latest/usage/scripts/#env_file
I've personally used this as a way to not use tools like dotenv, since I usually need them locally and not on production 😊 (plus is one less thing to worry about)
We would really love teh ability to load from a .env file for the same reason as above - we use a lot of our applications configuration in .env files.
Supporting this in uv run
seems like it could be really useful to me. When I'm working with Django I often have it pick up os.environ["..."]
for things like API keys needed by my application. uv run ./manage.py runserver
to pick up those from a .env
file could be a really nice pattern.
I am also interested in the uv run
support. I am coming from Poetry and currently use this plugin to achieve the same flow to pick up env variables from the .env
at the project root.
I think it would be useful to have a flag like --env-file
or some section in the config file like PDM does.
But why limit it to uv run
only? uv
uses a set of environment variables in place of command line arguments, that way you can use different definitions for uv
depending on your environment. I personally think it would be useful for situations like when you use a custom packages index for example.
I am also interested in the
uv run
support. I am coming from Poetry and currently use this plugin to achieve the same flow to pick up env variables from the.env
at the project root.
Hi, I was also migrating from poetry to uv, and currently use taskfile to handle .env variable passing to uv run
Example of the taskfile
version: "3"
dotenv: [".env"]
tasks:
manage:
cmds:
- uv run ./backend/manage.py {{.CLI_ARGS}}
and then i can run stuff like
task manage -- makemigraitons
task manage -- shell_plus
etc
Another user-case: I supply environment variables to my dev server with a .env file. I've been using poetry run bin/serve-dev
to start things with a dotenv loading plugin for poetry.
uv run bin/serve-dev
auto-loading my .env file would be nice, but to be honest uv run --env-file .env bin/serve-dev
is fine too, if you want to limit the blast radius of this change.
After the migration from pipenv
to uv
our local Ansible environments are now broken as uv
does not read the .env
file.
@zanieb @charliermarsh After reading this thread it looks like that missing feature is a real showstopper?
Here's a non-Python example: Node JS added native support to this feature in September 2023 (changelog) and it has been supported by Bun/Deno for a while now.
My use case is to use a separate project-specific Conan Local Cache when entering the respective venv. This simply means setting the CONAN_USER_HOME
env var to the venv path with source .venv/bin/activate
.
Currently, this is done by fumbling this into the generated activate/deactivate scripts. These scripts also vary between the Python versions so this also needs to be tested and updated for new Python versions :-(
I'm not sure if my use case is related, but I really enjoy pyenv
automatically activating a specific venv if it finds a .python-version
file in the directory. I guess the same can be achieved with .env
, but maybe there is already a way to use uv
to auto-activate envs? Or is this not necessary anymore with uv run
(sorry for my ignorance, I haven't really used uv
much yet)?
Or is this not necessary anymore with uv run (sorry for my ignorance, I haven't really used uv much yet)?
It shouldn't be needed if you use uv run
, though it's a bit more nuanced than that. Feel free to open a new issue if you run into problems there.
I like Brett Cannon's and Jeff Triplett's takes on .env
files, which identify the orthogonal purposes of using such files for configuration and for secrets. They also identify the issue with the lack of a standard of .env
files, e.g. path separator variation and other nuances.
Brett posed the question, could we standardize on TOML for this? I think if uv
picks this up, it could be done in stages, supporting .env
today but with no promises on the unstandardized format, then eventually incorporating a more opinionated take inside pyproject.toml
in a subtable of tool.uv
.
Secrets management is it's own beast, of course.
The upside to uv
tackling lockfiles is the standards discussion on that is already pretty mature, but there's no clear consensus on standardizing something for .env
, so maybe that's a bridge too far.
@blakeNaccarato IMHO it is widely understood that .env-files should have a bash-compatible syntax. Besides that, there should not be anything language or tool specific.
Brett posed the question, could we standardize on TOML for this?
The great thing about ruff is that it's essentially a drop-in replacement for pylint and other tools you were probably using before.
Doing the same with .env files would be a DX win.
Championing an emergent new format would be an interesting idea, as a separate and second step, but until it becomes comparable to .env in terms of adoption, it seems like a DX loss to deprecate .env.
I use direnv for this exact use-case. It is installed as a global binary on pretty much any OS, then activates any .envrc
or optionally also .env
when the folder is entered. This is a mature tool that does exactly this one job very well, with the bonus of not being restricted to only Python as it works on any folder, anywhere.
The big plus for me is that you need to explicitly enable the file for each folder (once off check) so it will not activate any random file you get in a cloned repo.
uv
doesn't need to do absolutely everything :wink:
Unfortunately i have to use Windows so direnv won't work for me.
Unfortunately i have to use Windows so direnv won't work for me.
I haven't tried it myself, but there is a windows download using winget
offered, even though it does say only '*nix' compatible on their github 🤷♂️. Ignore me if you've tried this and it didn't work :grin:
EDIT: people have used it with varying degrees of success, this gist has a walk-through
EDIT 2: I had a bit of time so I booted into windows, updated powershell as it doesn't work in the default version, traced and sorted the issues it has by default and jumped through hoops (needs 3 badly documented env var set). Then came to the conclusion it is totally borked under windows. Sorry, carry on :grin:
I wrote an isolated script for syncing .env
with a tool.dev.env
table in pyproject.toml
(by default, but it's configurable via CLI) that you can run in a shell profile or in your shell initialization flow. It's barebones and only supports environment variables hard-coded in your pyproject.toml
(e.g. no secrets), but I'm sure someone could extend it into a full utility. See also dump-env
for a feature set supporting secrets and .env
templating.
I’m supportive of adding this to uv run. It would also enforce that the point is not to configure uv but to configure the underlying commands.
(I think it’s not too difficult to add and there’s clearly a lot of demand.)
I think there are two options here:
.env
in uv run
(but not elsewhere). So, .env
would be for providing runtime environment variables, but not for configuring uv itself..env
everywhere.My inclination is to do the former (and I think we could get consensus on + ship that quickly). The downside is that you still need to provide UV_
-prefixed environment variables (which could include credentials) through some other mechanism.
Maybe we start with (1) and we can always expand to (2) if it's well-motivated.
If we do (1), my preference would be:
.env
--env-file
(or UV_ENV_FILE
) to override the file.--no-env-file
and UV_NO_ENV_FILE
to disable it.(In other words, I think it should be opt-out, not opt-in.)
For my use cases, option 1 is sufficient. I only need .env
files to be loaded at uv run
time, and I would prefer uv run
do so by default, which is equivalent to pipenv
's pipenv run
behavior. Offering opt-out flags seems like a good move.
With regard to credentials, which is often my reason for using a .env
file in development, could you explain more about this statement?
My inclination is to do the former (and I think we could get consensus on + ship that quickly). The downside is that you still need to provide UV_-prefixed environment variables (which could include credentials) through some other mechanism.
In that scenario, if I had a development credential that was previously kept in an un-committed/.gitignored as, say, GOOGLE_API_KEY=123456
would I need to prefix GOOGLE_API_KEY
with UV_
for uv to load it? Or are you talking only about env vars for configuring uv?
Or are you talking only about env vars for configuring uv?
I'm only referring to these. In other words, we'd pass the environment variables onwards to whatever uv run
runs, but we wouldn't use them within uv itself.
Ah, OK. That makes sense, and requiring UV_
vars to be set elsewhere is OK for my needs. If I configure uv, I usually do so with env vars defined in my shell profile, not per-project in a .env
file.
(In other words, I think it should be opt-out, not opt-in.)
I'd prefer the opposite, though I'm probably in the minority. I tend to exclusively use virtualenvs (and with direnv
I don't even have to activate them), and a fair percentage of my code tends to run as services or sockets which would stil need a dotenv
-type solution so i rarely use uv run
even in development. I think making a new feature optional at least at first (if it's configurable by the uv.toml
and well documented this should be usable?).
More importantly, direnv
for example requires explicit permission (once) to load any .env
file - i'd not like any repo i cloned to be able to set any ENV var it wants - overwrite PYTHONHOME/PYTHONPATH or system stuff?
@charliermarsh's recommendation (start with # 1, and make it opt-out) makes a lot of sense to me.
I think there are two options here:
- Respect
.env
inuv run
(but not elsewhere). So,.env
would be for providing runtime environment variables, but not for configuring uv itself.- Respect
.env
everywhere.My inclination is to do the former (and I think we could get consensus on + ship that quickly). The downside is that you still need to provide
UV_
-prefixed environment variables (which could include credentials) through some other mechanism.Maybe we start with (1) and we can always expand to (2) if it's well-motivated.
If we do (1), my preference would be:
- Always read
.env
- Allow users to pass
--env-file
(orUV_ENV_FILE
) to override the file.- Add
--no-env-file
andUV_NO_ENV_FILE
to disable it.(In other words, I think it should be opt-out, not opt-in.)
Option (2) is exactly what tools like Docker do, so a big 👍 here.
With option (1) we will already get most of the use cases for running local (development) run
commands. But option (2) will be needed for e.g. build
commands for smaller projects that don't use a CI/CD pipeline, yet. I think that option will also fulfill their needs and the env
discussion can be closed as a happy end 😀
IMHO without option 2 that topic will pop up sooner or later again and the discussion begins from the start...
With option (1) we will already get most of the use cases for running local (development)
run
commands. But option (2) will be needed for e.g.build
commands for smaller projects that don't use a CI/CD pipeline, yet. I think that option will also fulfill their needs and theenv
discussion can be closed as a happy end 😀IMHO without option 2 that topic will pop up sooner or later again and the discussion begins from the start...
Fully agree. uv run
should give the same results as executing the command after activating the venv first to not confuse people.
Just to add a thought here, it would be really handy to define a project .env file next to the lockfile that could set the UV_EXTRA_INDEX_URL
and UV_KEYRING_PROVIDER
variables. This way I could switch between open-source project and work projects without editing my shell environment
I'm always using .env to set project-specific variables for pip, twine,django,uvicorn ... in each project.
All my projects are isolated application directories (think virtualenv on steroids),
where we actively try hard to avoid on anything depending in a users $HOME-dir.
(Avoiding: "it works on my machine" ... because of your .dotfiles)
My first use-cases would be to set the env-variables equivalents of PIP* and "TWINE"
for "uv pip install" + "uv publish"
mainly to set credentials that can not be used directly in a pyproject.toml.
For example:
in .env
PERSONAL_ACCESS_TOKEN='<personal-access-token>'
TWINE_REPOSITORY_URL='<url>'
TWINE_PASSWORD='<password>'
TWINE_USERNAME='<username>'
then in pyproject.toml if https://github.com/astral-sh/uv/issues/5734 is implemented
[tool.uv]
index-url = "https://__token__:${PERSONAL_ACCESS_TOKEN}@gitlab.com/..."
Respect .env everywhere.
I'm favor to always read the .env to also support the project-specific UV_*-variables to override the global default config.
Allow users to pass --env-file (or UV_ENV_FILE) to override the file.
Yes.
Add --no-env-file and UV_NO_ENV_FILE to disable it. In other words, I think it should be opt-out, not opt-in.
Yes. opt-out via an externally managed variable makes more sense logically (because all other vars are already managed outside .env) than opt-in (use the .env) via a non-.dotenv managed variable.
In https://github.com/astral-sh/uv/pull/8263, some commenters are making the case that this should be opt-in rather than opt-out. I want to open up the conversation for more opinions here before this ships.
I personally feel like reading any .env
or otherwise named env file by default, without any input or config telling uv run
to do so, is undesirable.
I also feel like having simple pyproject.toml
config like read_dotenv_files = true
(or equivalent CLI option) should opt-in to that behavior being automatic.
that allows projects (or users of projects) to trivially opt-in to dotenv style reading if it's desired/utilized and poses no risk for projects that don't desire to use it.
I personally feel like reading any .env or otherwise named env file by default, without any input or config telling uv run to do so, is undesirable.
I also feel like having simple pyproject.toml config like read_dotenv_files = true (or equivalent CLI option) should opt-in to that behavior being automatic.
Yeah. I commented similar on the other thread and suggested just a global config option but this would be a good solution too with a cli option to set it (like we have for app or lib already). Either way, opt in rather than out.
A couple of Python tools come to mind that do load .env
files by default. For example, pipenv run ...
and flask run
do this out of the box in opt-out mode. pipenv considers the PIPENV_DONT_LOAD_ENV
env var, and Flask uses FLASK_SKIP_DOTENV
. As someone who uses pipenv for day-to-day application development, I am excited for uv to gain auto-loading of .env
files by default. Having to add a pyproject.toml
entry like read_dotenv_files = true
would be OK, though I'm sure I would forget to add it occasionally in new projects.
For example, pipenv run ... and flask run do this out of the box in opt-out mode.
'Flask run' is however less of a tool in the 'uv' mold than very specific to Flask users, not going to be used by EVERY Python project and environment you write. It's more equivalent to 'django-admin'.
I'm not saying this is a bad thing, just make it opt-in so there are no surprises. The large majority of users generally don't read release notes🤷♂️
One facet of it is that uv run
is very versatile, and not just used in uv projects, but could be used in any directory, and with/without existing project or virtual environment. And you can also use uv run
to run things in a different directory like uv run --python ./path/to/venv
or uv run --project foo
which also puts some distance between the .env
file and the installed python software. Anchoring env file support to a configuration or a uv project would be a more consistent model, possibly(?)
After the migration from pipenv
to uv
our local Ansible environments are broken as uv
does not read the .env
file by default.
Also Docker ~and Ansible~ read .env
by default.
Edit: My bad, Ansible is not loading the environment via .env
, that's exactly the use case why I need uv
to load it for Ansible on my local dev environment! 😀
In my case (what our company does): The .env
files are only needed for local development where users also run uv run ...
manually. Opt-in would be no comfort from the users perspective especially when projects will be migrated from tools like pipenv
to uv
. Also production systems get different environment variables that will be set for the run and will not use any .env
file and if I would like to change that behaviour I will use the UV_xx
environment variables.
So, maybe this approach will be accepted:
1) Use opt-out by default, so uv
reads the .env
file by default
2) Add UV_xx
environment variables for not reading and reading .env
files: You already planned this feature
3) Add another option for the pyproject.toml
file so reading an .env
file for a project can be set to opt-in instead of the default opt-out
@seapagan @AndreuCodina Would option 3 something you would like to see in uv
?
@TheRealBecks
My preferred choice would still be opt-in by default but I can see that's unlikely to happen 😂
I've never really used 'pipenv' (went pip to poetry to uv) so the whole concept of auto-loading env files is strange to me.
Yeah, option 3 would work but I'd really love a global setting in the configuration file too, so I can opt-out for every project and just forget about it. I've not sat down and gone through the PR in depth yet so apologies if this is already there.
I don't really care as long as there is an env var which can overwrite the behaviour. I let direnv load .envs and so would disable it for uv.
We also have some projects where the .env is in a parent folder, not the repo folder. So I guess for these repos, it wouldn't work anyways.
Opt-in would be no comfort from the users perspective especially when projects will be migrated from tools like
pipenv
touv
.
@TheRealBecks fwiw this doesn't track with me as an argument for opt-in by default.
If you're already having to do various other config changes and generate new lock files (you almost certainly are) and anything else in the migration process I struggle to see how marginally tweaking the new tool's config isn't entirely expected anyway. Migrating between these tools isn't overly hard, but it's never as simple as literally just calling a different tool with 0 config changes either.
Isn't placing a file in the directory you are running uv
from named exactly .env
a sign you're opting-in already? If you have a .env
-formatted file that you don't want auto-loaded by uv
, then just name it something else.
Asking uv
to force users to pass an "opt-in" flag every time (or set it via uv.toml
) to load .env
files is like saying git
should require that users pass a --use-ignore-file
(or equivalent in .git/config
) to use .gitignore
, or asking bash
to require users to pass some flag to read .bashrc
, etc.
I understand there are cases where opting out or explicitly passing a specific file would be needed (e.g. multiple dotenv variants (.env.stage
, .env.dev
, etc), dotenv files in other directories, etc) but having uv
auto-load a plain .env
file in the cwd is an absolutely reasonable default IMHO.
I can give a practical example:
.env
and .env.myenvironment
is the Python convention to store settings used by the code, and the code should decide if it gives priority to .env files or other sources. The settings must be managed by code, and the execution of the code the simplest and the same in all environments: uv run
.
myenvironment
is a value obtained with an environment variable, so:
dotenv = DotEnvSettingsSource(
settings_cls,
env_file=[".env", f".env.{os.environ["PYTHON_ENVIRONMENT"]"],
env_nested_delimiter="__",
case_sensitive=True,
)
Then, even if you're using pydantic-settings
or a runner tool (uv
), you'll need an environment variable.
So, we do agree, we need to provide an environment variable, but the issue is how.
With code is simple, but verbose (because we need this file in every project):
class ApplicationEnvironment(StrEnum):
LOCAL = "Local"
DEVELOPMENT = "Development"
STAGING = "Staging"
PRODUCTION = "Production"
@staticmethod
def get_current() -> str:
return os.getenv("PYTHON_ENVIRONMENT", ApplicationEnvironment.LOCAL)
and it'll be used this way:
dotenv = DotEnvSettingsSource(env_file=[".env", f".env.{ApplicationEnvironment.get_current()}"],
This environment variable will be used for configuring the code itself, e.g. not adding telemetry if you're in localhost, adding secrets from Azure Key Vault if you're not in localhost, not showing the Swagger documentation in production, etc.
docs_url = (
"/docs"
if ApplicationEnvironment.get_current() != ApplicationEnvironment.PRODUCTION
else None
)
app = FastAPI(
docs_url=docs_url,
redoc_url=None,
)
So, the thing is, when we deploy an application, we provide an environment variable on the cloud or with docker run
, and the proper .env.myenvironment
file is read.
But in localhost we can't do that, so where do we specify the environment variable?
.env files are for code configuration, so use another thing: pyproject.toml, mynewfile.json, etc.
In .NET we have a command for everything called dotnet
, and it's the uv
equivalent.
The launchSettings.json file is used in ASP.NET Core to configure how an application is launched during development, and you can specify environment variables and tool dotnet
configurations.
In summary, uv
or your framework should provide a (configurable) default environment variable to run the project, but not from the code settings. And this environment variable will be different in each environment, not relying on different uv
parameters to read from here of there.
What environment variables do you need? TBH I don't think more than the PYTHON_ENVIRONMENT variable is necessary. On the cloud, with user-assigned managed identities, you add for example the AZURE_CLIENT_ID
environment variable, but when you run the code in localhost, you don't set it by hand, you execute az login
.
(Candidly, I don't find this framing very convincing because dotenv loading is something that other runners support and is much-used and appreciated by users of those tools.)
In my workflow I make heavy use of env-files.
several tools support .env out of the box:
It would be nice if uv could support .env-files in the $CWD or parent-dirs. see https://crates.io/crates/dotenv