Skip to content

Commit 4ca6e22

Browse files
committed
Added multi-env file capabillities
1 parent ccac681 commit 4ca6e22

11 files changed

Lines changed: 143 additions & 98 deletions

File tree

.github/workflows/test.yml

Lines changed: 4 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,16 @@
11
name: Test
2-
3-
on:
4-
push:
5-
branches:
6-
- main
7-
pull_request:
8-
branches:
9-
- main
10-
2+
on: [push, pull_request]
113
jobs:
124
build:
135
runs-on: ubuntu-latest
146
strategy:
157
matrix:
16-
python-version: ["3.8", "3.9", "3.9", "3.10", "3.11", "3.12"]
8+
python-version: ["3.10", "3.11", "3.12", "3.13"]
179

1810
steps:
19-
- uses: actions/checkout@v1
11+
- uses: actions/checkout@v4
2012
- name: Set up Python ${{ matrix.python-version }}
21-
uses: actions/setup-python@v2
13+
uses: actions/setup-python@v5
2214
with:
2315
python-version: ${{ matrix.python-version }}
2416
- name: Install dependencies

.gitignore

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
*.pyc
2-
.tox/
2+
/.tox
33
.coverage
44
__pycache__
5-
castaway.egg-info/
6-
venv/
7-
htmlcov/
8-
temp/
5+
/src/castaway.egg-info
6+
/venv
7+
/htmlcov
8+
/temp
9+
/dist
10+
/.pytest_cache
911
.python-version

LICENSE

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
MIT License
22

3-
Copyright (c) 2021 David A Krauth
3+
Copyright (c) 2025 David A Krauth
44

55
Permission is hereby granted, free of charge, to any person obtaining a copy
66
of this software and associated documentation files (the "Software"), to deal

pyproject.toml

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
[build-system]
2+
requires = ["setuptools >= 61.0"]
3+
build-backend = "setuptools.build_meta"
4+
5+
[project]
6+
name = "castaway"
7+
version = "1.1.0"
8+
description = "Simple wrapper for dotenv, with casting"
9+
requires-python = ">=3.10"
10+
readme = { file = "README.rst", content-type = "text/x-rst" }
11+
license = { file = "LICENSE" }
12+
authors = [
13+
{ name = "David Krauth", email = "dakrauth@gmail.com"},
14+
]
15+
classifiers = [
16+
"Development Status :: 5 - Production/Stable",
17+
"Environment :: Web Environment",
18+
"Environment :: Console",
19+
"Framework :: Django",
20+
"License :: OSI Approved :: MIT License",
21+
"Programming Language :: Python",
22+
"Programming Language :: Python :: 3 :: Only",
23+
"Programming Language :: Python :: 3.10",
24+
"Programming Language :: Python :: 3.11",
25+
"Programming Language :: Python :: 3.12",
26+
"Programming Language :: Python :: 3.13",
27+
]
28+
dependencies = ["python-dotenv[cli]==1.0.1"]
29+
30+
[project.urls]
31+
Homepage = "http://github.com/dakrauth/castaway"
32+
33+
[project.optional-dependencies]
34+
django = [ "dj-email-url==1.0.6", "dj-database-url==2.3.0" ]
35+
test = ["pytest-cov", "tox"]
36+
37+
[tool.setuptools]
38+
package-dir = { "" = "src" }
39+
packages = [ "castaway" ]
40+
41+
[tool.ruff]
42+
line-length = 100
43+
indent-width = 4
44+
45+
[tool.ruff.lint]
46+
ignore = ["E741"]

run

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
#!/usr/bin/env bash
2+
3+
set -o errexit
4+
set -o pipefail
5+
TIMEFORMAT="Task completed in %3lR"
6+
BIN="./venv/bin"
7+
8+
function help {
9+
echo "Options:"
10+
echo " init: Setup dev environment"
11+
echo " lint: Linter"
12+
echo " check: Formatting checks"
13+
echo " format: Run formatter"
14+
echo " test: Execute tests"
15+
echo " coverage: Run tests with code coverage"
16+
echo " clean: Remove dev, test, and build artifacts"
17+
}
18+
19+
function init {
20+
python3 -m venv --prompt castaway venv
21+
"${BIN}/python" -m pip install -e ".[django,test]"
22+
}
23+
24+
function lint {
25+
"${BIN}/ruff" check castaway.py tests
26+
}
27+
28+
function check {
29+
"${BIN}/ruff" format --check --diff src/castaway tests
30+
}
31+
32+
function format {
33+
"${BIN}/ruff" format src/castaway tests
34+
}
35+
36+
function test {
37+
"${BIN}/pytest" -s -Werror tests "$@"
38+
}
39+
40+
function clean {
41+
rm -rf .ruff_cache .tox htmlcov .coverage dist pytest_cache src/castaway.egg-info
42+
}
43+
44+
function coverage {
45+
"${BIN}/pytest" \
46+
--cov-report html \
47+
--cov-report term \
48+
--cov=castaway
49+
}
50+
51+
if [ -z "$1" ]; then
52+
help
53+
else
54+
what=$1
55+
shift
56+
time ${what:-help} "$@"
57+
fi

setup.cfg

Lines changed: 0 additions & 9 deletions
This file was deleted.

setup.py

Lines changed: 0 additions & 41 deletions
This file was deleted.

castaway.py renamed to src/castaway/__init__.py

Lines changed: 16 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,12 @@
11
import os
2+
from pathlib import Path
23
import dotenv
34

45
required = object()
56

67

78
def cast_bool(val):
8-
return (
9-
val.lower() in {"1", "yes", "true", "y", "on"}
10-
if isinstance(val, str)
11-
else bool(val)
12-
)
9+
return val.lower() in {"1", "yes", "true", "y", "on"} if isinstance(val, str) else bool(val)
1310

1411

1512
def cast_list(val):
@@ -30,8 +27,11 @@ def cast_django_email(val):
3027

3128
class Config:
3229
def __init__(self, filename=".env", **castings):
33-
self.filename = filename
34-
self.found_path = None
30+
if isinstance(filename, (str, Path)):
31+
filename = [filename]
32+
33+
self.filename = [str(f) for f in filename]
34+
self.found_path = []
3535

3636
self.castings = {
3737
bool: cast_bool,
@@ -40,12 +40,15 @@ def __init__(self, filename=".env", **castings):
4040
"django_email": cast_django_email,
4141
}
4242
self.castings.update(**castings)
43-
44-
if os.path.exists(self.filename):
45-
self.found_path = self.filename
46-
else:
47-
self.found_path = dotenv.find_dotenv(self.filename, usecwd=True)
48-
self.values = dotenv.dotenv_values(self.found_path, verbose=True)
43+
self.values = {}
44+
for path in self.filename:
45+
if os.path.exists(path):
46+
found = path
47+
else:
48+
found = dotenv.find_dotenv(path, usecwd=True)
49+
50+
self.found_path.append(found)
51+
self.values.update(**dotenv.dotenv_values(found, verbose=True))
4952

5053
def add_castings(self, **kwargs):
5154
self.castings.update(kwargs)

tests/.env2

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
CASTAWAY_DECIMAL=3.14159

tests/test_castenv.py

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import os
2+
import decimal
23
import pytest
34
import castaway
45

@@ -13,12 +14,14 @@ def set_cwd():
1314

1415
@pytest.fixture
1516
def cfg(set_cwd):
16-
return castaway.Config()
17+
return castaway.Config(filename=[".env", ".env2"])
1718

1819

1920
def test_default_config(set_cwd):
2021
config = castaway.config
22+
config.add_castings(decimal=decimal.Decimal)
2123
assert config("CASTAWAY_INT", cast=int) == 23
24+
assert config("CASTAWAY_DECIMAL", cast="decimal") == decimal.Decimal("2.3")
2225

2326

2427
def test_bool(cfg):
@@ -39,10 +42,8 @@ def test_list(cfg):
3942

4043

4144
def test_custom_cast(cfg):
42-
import decimal
43-
4445
cfg.add_castings(decimal=decimal.Decimal)
45-
assert cfg("CASTAWAY_DECIMAL", cast="decimal") == decimal.Decimal("2.3")
46+
assert cfg("CASTAWAY_DECIMAL", cast="decimal") == decimal.Decimal("3.14159")
4647

4748

4849
def test_override():
@@ -61,7 +62,9 @@ def test_required_failure(cfg):
6162

6263

6364
def test_dj_database(cfg):
64-
assert cfg("CASTAWAY_DJ_DATABASE", cast="django_db") == {
65+
result = cfg("CASTAWAY_DJ_DATABASE", cast="django_db")
66+
result.pop("DISABLE_SERVER_SIDE_CURSORS", None)
67+
assert result == {
6568
"ENGINE": "django.db.backends.mysql",
6669
"OPTIONS": {"init_command": "SET storage_engine=InnoDB", "charset": "utf8mb4"},
6770
"NAME": "dbname",

0 commit comments

Comments
 (0)