Repository: mpeteuil/poetry-dotenv-plugin
Branch: main
Commit: 75a6989512f2
Files: 9
Total size: 11.5 KB
Directory structure:
gitextract_xbolhul7/
├── .github/
│ └── workflows/
│ ├── build.yml
│ └── release.yml
├── LICENSE
├── README.md
├── poetry_dotenv_plugin/
│ ├── __init__.py
│ └── dotenv_plugin.py
├── pyproject.toml
└── tests/
├── __init__.py
└── test_system.sh
================================================
FILE CONTENTS
================================================
================================================
FILE: .github/workflows/build.yml
================================================
# Heavily based on the Poetry main Github Actions workflow
# https://github.com/python-poetry/poetry/blob/1.2.0a1/.github/workflows/main.yml
name: "CI"
on:
push:
branches:
- main
pull_request:
branches:
- '**'
jobs:
tests:
name: ${{ matrix.os }} / ${{ matrix.python-version }}
runs-on: ${{ matrix.os }}-latest
strategy:
matrix:
os: [Ubuntu, MacOS]
python-version: ["3.7", "3.8", "3.9", "3.10", "3.11", "3.12"]
defaults:
run:
shell: bash
steps:
- uses: actions/checkout@v4
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v4
id: setup-python
with:
python-version: ${{ matrix.python-version }}
- name: Install poetry
run: |
if [[ "${{ matrix.python-version }}" == "3.7" ]]; then
curl -sSL https://install.python-poetry.org | python - --version 1.5.1 -y
else
curl -sSL https://install.python-poetry.org | python - -y
fi
- name: Update PATH
run: echo "$HOME/.local/bin" >> $GITHUB_PATH
- name: Configure poetry
run: poetry config virtualenvs.in-project true
- name: Set up cache
uses: actions/cache@v3
id: cache
with:
path: .venv
key: venv-${{ runner.os }}-${{ steps.setup-python.outputs.python-version }}-${{ hashFiles('**/poetry.lock') }}
- name: Ensure cache is healthy
if: steps.cache.outputs.cache-hit == 'true'
run: timeout 10s poetry run pip --version || rm -rf .venv
- name: Install dependencies
run: poetry install --no-root
- name: Install poetry-dotenv-plugin
run: poetry self add "$GITHUB_WORKSPACE"
- name: Run system tests
run: tests/test_system.sh
================================================
FILE: .github/workflows/release.yml
================================================
# Based on
# https://github.com/python-poetry/poetry/blob/a0cc7d6b9ea9b59203ac01e4ac641643dc7c9c7a/.github/workflows/release.yml
name: Release
on:
push:
tags:
- '*.*.*'
jobs:
release:
name: Release
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Set up Python 3.11
uses: actions/setup-python@v4
with:
python-version: "3.11"
- name: Install Poetry
run: curl -sSL https://install.python-poetry.org | python - -y
- name: Update PATH
run: echo "$HOME/.local/bin" >> $GITHUB_PATH
- name: Build project for distribution
run: poetry build
- name: Check Version
id: check-version
run: |
[[ "$(poetry version --short)" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]] \
|| echo "prerelease=true" >> "$GITHUB_OUTPUT"
- name: Create Release
uses: ncipollo/release-action@v1
with:
artifacts: "dist/*"
token: ${{ secrets.GITHUB_TOKEN }}
draft: false
prerelease: steps.check-version.outputs.prerelease == 'true'
- name: Publish to PyPI
env:
POETRY_PYPI_TOKEN_PYPI: ${{ secrets.PYPI_TOKEN }}
run: poetry publish
================================================
FILE: LICENSE
================================================
Copyright (c) 2021 Michael Peteuil
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
================================================
FILE: README.md
================================================
# Poetry Dotenv Plugin
[](https://github.com/mpeteuil/poetry-dotenv-plugin/actions/workflows/build.yml)
A [Poetry](https://python-poetry.org/) plugin that automatically loads environment variables from `.env` files into the environment before poetry commands are run.
Supports Python 3.7+[^1]
```sh
$ cat .env
MY_ENV_VAR='Hello World'
$ poetry run python -c 'import os; print(os.environ.get("MY_ENV_VAR"))'
Hello World
```
This plugin depends on the [`python-dotenv` package](https://github.com/theskumar/python-dotenv) for its functionality and therefore also supports features that `python-dotenv` supports. Interpolating variables using POSIX variable expansion for example.
### Origins
Initial implementation based on the event handler application plugin example in the [Poetry docs](https://python-poetry.org/docs/plugins/#event-handler).
## Install
```sh
poetry self add poetry-dotenv-plugin
```
### Coming from Pipenv
If you are transitioning from `pipenv` there shouldn't be much to change with regard to the `.env` loading. If you were a user of [`pipenv`'s environment variables](https://pipenv.pypa.io/en/latest/advanced/#automatic-loading-of-env) to control `.env` loading then you can use the analogous environment variables listed below.
Pipenv env var | Poetry env var
-------------- | ----------------------
PIPENV_DOTENV_LOCATION | POETRY_DOTENV_LOCATION
PIPENV_DONT_LOAD_ENV | POETRY_DONT_LOAD_ENV
### Overriding existing environment variables
By default, this plugin will override existing environment variables. This is because this plugin was built to make onboarding for users coming from `pipenv` as seamless as possible. If you want to prevent existing environment variables from being overridden, you can set the `POETRY_DOTENV_DONT_OVERRIDE` environment variable to `true`.[^2]
[^1]: Python 3.7 is supported only when using Poetry < [1.6.0](https://python-poetry.org/history/#160---2023-08-20), which [dropped support for Python 3.7](https://github.com/python-poetry/poetry/pull/7674).
[^2]: See [#16](https://github.com/mpeteuil/poetry-dotenv-plugin/pull/16) for background.
================================================
FILE: poetry_dotenv_plugin/__init__.py
================================================
================================================
FILE: poetry_dotenv_plugin/dotenv_plugin.py
================================================
import os
from cleo.events.console_events import COMMAND
import dotenv
from poetry.console.application import Application
from poetry.console.commands.env_command import EnvCommand
from poetry.plugins.application_plugin import ApplicationPlugin
class DotenvPlugin(ApplicationPlugin):
def activate(self, application):
application.event_dispatcher.add_listener(COMMAND, self.load_dotenv)
def load_dotenv(
self,
event,
event_name,
dispatcher
):
POETRY_DONT_LOAD_ENV = bool(os.environ.get("POETRY_DONT_LOAD_ENV"))
command = event.command
if not isinstance(command, EnvCommand) or POETRY_DONT_LOAD_ENV:
return
POETRY_DOTENV_LOCATION = os.environ.get("POETRY_DOTENV_LOCATION")
io = event.io
if io.is_debug():
io.write_line("Loading environment variables.")
path = POETRY_DOTENV_LOCATION or dotenv.find_dotenv(usecwd=True)
POETRY_DOTENV_DONT_OVERRIDE = os.environ.get("POETRY_DOTENV_DONT_OVERRIDE", "")
DOTENV_OVERRIDE = not POETRY_DOTENV_DONT_OVERRIDE.lower() in (
"true",
"1",
)
dotenv.load_dotenv(dotenv_path=path, override=DOTENV_OVERRIDE)
================================================
FILE: pyproject.toml
================================================
[tool.poetry]
name = "poetry-dotenv-plugin"
version = "0.2.0"
description = "A Poetry plugin to automatically load environment variables from .env files"
authors = ["Michael Peteuil "]
license = "MIT"
readme = "README.md"
packages = [{include = "poetry_dotenv_plugin"}]
homepage = "https://github.com/mpeteuil/poetry-dotenv-plugin"
repository = "https://github.com/mpeteuil/poetry-dotenv-plugin"
keywords = ["poetry", "poetry-plugin", "plugin", "dotenv"]
classifiers = [
"Topic :: Software Development",
"Topic :: System :: Systems Administration",
"Topic :: Utilities",
]
[tool.poetry.dependencies]
python = ">=3.7,<4"
poetry = ">=1.2.0a1"
python-dotenv = ">=0.10.0"
[tool.poetry.group.dev.dependencies]
pytest = "^6.2.3"
[build-system]
requires = ["poetry-core"]
build-backend = "poetry.core.masonry.api"
[tool.poetry.plugins."poetry.application.plugin"]
poetry-dotenv-plugin = "poetry_dotenv_plugin.dotenv_plugin:DotenvPlugin"
================================================
FILE: tests/__init__.py
================================================
================================================
FILE: tests/test_system.sh
================================================
#!/usr/bin/env bash
function create_dotenv_file() {
# Setup .env file so the plugin can do its job
echo "export MY_ENV_VAR='foo'" > .env
}
function delete_dotenv_file() {
# Tear down .env file since we no longer need it
rm -f .env
}
function test_end_to_end_system_with_default_dotenv_file() {
# Setup
local expected
expected='foo'
create_dotenv_file
local output
output=$(poetry run python -c "import os; print(os.environ['MY_ENV_VAR'])")
# Cleanup
delete_dotenv_file
if [ "$expected" = "$output" ]; then
printf "test_end_to_end_system_with_default_dotenv_file: PASSED\n"
else
printf "Expected '$expected', but got '%s'.\n" "$output"
exit 1
fi
}
function test_end_to_end_system_with_dotenv_location_override() {
# Setup
local expected
expected='bar'
local new_dotenv_path
new_dotenv_path="$PWD/tests/tmp"
create_dotenv_file
# Override the file that was just created
mkdir -p "$new_dotenv_path" && echo "export MY_ENV_VAR='bar'" > "$new_dotenv_path/.env"
local output
output=$(export POETRY_DOTENV_LOCATION="$new_dotenv_path/.env" && poetry run python -c "import os; print(os.environ['MY_ENV_VAR'])")
# Cleanup
rm -rf "$new_dotenv_path"
delete_dotenv_file
if [ "$expected" = "$output" ]; then
printf "test_end_to_end_system_with_dotenv_location_override: PASSED\n"
else
printf "Expected '$expected', but got '%s'.\n" "$output"
exit 1
fi
}
function test_end_to_end_system_with_default_env_overrides() {
# Setup
local expected
expected='foo'
create_dotenv_file
local output
output=$(export MY_ENV_VAR='bar' && poetry run python -c "import os; print(os.environ['MY_ENV_VAR'])")
# Cleanup
delete_dotenv_file
if [ "$expected" = "$output" ]; then
printf "test_end_to_end_system_with_default_env_overrides: PASSED\n"
else
printf "Expected '$expected', but got '%s'.\n" "$output"
exit 1
fi
}
function test_end_to_end_system_without_env_overrides() {
# Setup
local expected
expected='bar'
create_dotenv_file
local output
output=$(export MY_ENV_VAR='bar' POETRY_DOTENV_DONT_OVERRIDE=true && poetry run python -c "import os; print(os.environ['MY_ENV_VAR'])")
# Cleanup
delete_dotenv_file
if [ "$expected" = "$output" ]; then
printf "test_end_to_end_system_without_env_overrides: PASSED\n"
else
printf "Expected '$expected', but got '%s'.\n" "$output"
exit 1
fi
}
function test_end_to_end_system_without_loading_dotenv_file() {
# Setup
local expected
expected='Nonexistent Variable'
create_dotenv_file
local output
output=$(export POETRY_DONT_LOAD_ENV=true && poetry run python -c "import os; print(os.environ.get('MY_ENV_VAR', '$expected'))")
# Cleanup
delete_dotenv_file
if [ "$expected" = "$output" ]; then
printf "test_end_to_end_system_without_loading_dotenv_file: PASSED\n"
else
printf "Expected '$expected', but got '%s'.\n" "$output"
exit 1
fi
}
test_end_to_end_system_with_default_dotenv_file
test_end_to_end_system_with_dotenv_location_override
test_end_to_end_system_with_default_env_overrides
test_end_to_end_system_without_env_overrides
test_end_to_end_system_without_loading_dotenv_file