Comments (8)
I should add that I'm willing to contribute and help implement this feature.
from environs.
I updated the issue to include an idea on how you would invoke writing to the file in a Django app.
from environs.
I coded up the following POC that could be integrated in to the project.
import base64
import os
from datetime import datetime, timezone
from pathlib import Path
from typing import Union
import environs
class EnvWriter:
var_data: dict
write_dot_env_file: bool = False
def __init__(self, read_dot_env_file: bool = True, eager: bool = True, expand_vars: bool = False):
self.var_data = {}
self._env = environs.Env(eager=eager, expand_vars=expand_vars)
self.write_dot_env_file = self._env.bool("WRITE_DOT_ENV_FILE", default=False)
self._env = environs.Env(eager=eager, expand_vars=expand_vars)
self.base_dir = environs.Path(__file__).parent
if read_dot_env_file is True:
self._env.read_env(str(self.base_dir.joinpath(".env")))
def _get_var(self, environs_instance, var_type: str, environ_args: tuple = None, environ_kwargs: dict = None):
help_text = environ_kwargs.pop("help_text", None)
initial = environ_kwargs.pop("initial", None)
if self.write_dot_env_file is True:
self.var_data[environ_args[0]] = {
"type": var_type,
"default": environ_kwargs.get("default"),
"help_text": help_text,
"initial": initial,
}
try:
return getattr(environs_instance, var_type)(*environ_args, **environ_kwargs)
except environs.EnvError as e:
if self.write_dot_env_file is False:
raise e
def __call__(self, *args, **kwargs):
return self._get_var(self._env, var_type="str", environ_args=args, environ_kwargs=kwargs)
def __getattr__(self, item):
allowed_methods = [
"int",
"bool",
"str",
"float",
"decimal",
"list",
"dict",
"json",
"datetime",
"date",
"time",
"path",
"log_level",
"timedelta",
"uuid",
"url",
"enum",
"dj_db_url",
"dj_email_url",
"dj_cache_url",
]
if item not in allowed_methods:
return AttributeError(f"'{type(self).__name__}' object has no attribute '{item}'")
def _get_var(*args, **kwargs):
return self._get_var(self._env, var_type=item, environ_args=args, environ_kwargs=kwargs)
return _get_var
def write_env_file(self, env_file_path: Union[Path, str] = None, overwrite_existing: bool = False):
if env_file_path is None:
env_file_path = self.base_dir.joinpath(".env")
if env_file_path.exists() is True and overwrite_existing is False:
env_file_path = f"{env_file_path}.{datetime.now().strftime('%Y%m%d%H%M%S')}"
with open(env_file_path, "w") as f:
env_str = (
f"# This is an initial .env file generated on {datetime.now(timezone.utc).isoformat()}. Any environment variable with a default\n"
"# can be safely removed or commented out. Any variable without a default must be set.\n\n"
)
for key, data in self.var_data.items():
initial = data.get("initial", None)
val = ""
if data["help_text"] is not None:
env_str += f"# {data['help_text']}\n"
env_str += f"# type: {data['type']}\n"
if data["default"] is not None:
env_str += f"# default: {data['default']}\n"
if initial is not None and val == "":
val = initial()
if val == "" and data["default"] is not None:
env_str += f"# {key}={val}\n\n"
else:
env_str += f"{key}={val}\n\n"
f.write(env_str)
os.environ.setdefault("WRITE_DOT_ENV_FILE", "True")
env = EnvWriter(read_dot_env_file=False)
DEBUG = env.bool("DEBUG", default=False, help_text="Set Django Debug mode to on or off")
MAX_CONNECTIONS = env.int("MAX_CONNECTIONS", help_text="Maximum number of connections allowed to the database")
SECRET_KEY = env(
"SECRET_KEY",
initial=lambda: base64.b64encode(os.urandom(60)).decode(),
help_text="Django's SECRET_KEY used to provide cryptographic signing.",
)
ALLOWED_HOSTS = env.list("ALLOWED_HOSTS", default=[], help_text="List of allowed hosts that this Django site can serve")
INTERNAL_IPS = env.list("INTERNAL_IPS", default=["127.0.0.1"], help_text="IPs allowed to run in debug mode")
REDIS_URL = env("REDIS_URL", default="redis://redis:6379/0", help_text="Redis URL for connecting to redis")
DATABASES = {
"default": env.dj_db_url(
"DATABASE_URL",
default=f"sqlite:///{env.base_dir}/db.sqlite",
help_text="Database URL for connecting to database",
)
}
email = env.dj_email_url(
"EMAIL_URL",
default="smtp://[email protected]:[email protected]:587/?ssl=True&_default_from_email=President%20Skroob%20%[email protected]%3E",
help_text="URL used for setting Django's email settings",
)
env.write_env_file(overwrite_existing=True)
from environs.
The previous POC code generates the following .env
file.
# This is an initial .env file generated on 2023-09-01T19:06:59.482643+00:00. Any environment variable with a default
# can be safely removed or commented out. Any variable without a default must be set.
# Set Django Debug mode to on or off
# type: bool
# default: False
# DEBUG=
# Maximum number of connections allowed to the database
# type: int
MAX_CONNECTIONS=
# Django's SECRET_KEY used to provide cryptographic signing.
# type: str
SECRET_KEY=JtMmaa4NOcM6H84tXWjzVQmNobPWvVY5nahPcS17U6zftSkm9G2yO/GDHDUj7Sr0Q0s0lOsS32L/1FY0
# List of allowed hosts that this Django site can serve
# type: list
# default: []
# ALLOWED_HOSTS=
# IPs allowed to run in debug mode
# type: list
# default: ['127.0.0.1']
# INTERNAL_IPS=
# Redis URL for connecting to redis
# type: str
# default: redis://redis:6379/0
# REDIS_URL=
# Database URL for connecting to database
# type: dj_db_url
# default: sqlite:////opt/project/db.sqlite
# DATABASE_URL=
# URL used for setting Django's email settings
# type: dj_email_url
# default: smtp://[email protected]:[email protected]:587/?ssl=True&_default_from_email=President%20Skroob%20%[email protected]%3E
# EMAIL_URL=
from environs.
@sloria, any thoughts on this? I put a lot of thought and time into this.
I made another POC Pull Request into Django Base Site, if you want to look at that as well. It seems like something like this could be very helpful!
from environs.
Sorry for the delay in responding. I'd say that creating boilerplate .env
files is out of the scope of environs. This library is unopinionated and is used in a wide variety of use cases (not only web applications), so any template .env file is bound to be overprescriptive. Keeping this feature in app boilerplates like the one you linked to makes a lot of sense to me!
from environs.
@sloria, How does adding a way to generate an .env
file to any Python project, whether it's a web, CLI, or whatever type project make environs
more opinionated?
Any python project that you have to manually figure out where each env()
call is being made and determine the intent and reason behind the variable, creates a lot of extra wasted time, when all of that information could be be self documenting.
from environs.
environs scope is limited to parsing and validating envvars--it has no opinions on what those envvars are and what they're used for.
A command for writing a boilerplate .env file is the domain of project scaffolds.
from environs.
Related Issues (20)
- path parser rejects Path defaults HOT 1
- `TypeError: _field2method.<locals>.method()` using `environs[django]` HOT 3
- Variables expanding in dotenv files doesn't respect other sources of Env vars HOT 2
- Log values as they are returned HOT 6
- Bringing environments to subprocess HOT 2
- Default values and subcast
- Is TOML support wanted? HOT 1
- All environment variable names and associated values logged on unhandled env attribute error HOT 3
- errors with raise statements and basestring.... not python3 compatable? HOT 2
- fail if specified .env file does not exist HOT 2
- add "smtp+ssl://, smtp+tls://" supports to email_url HOT 1
- Default values defined in casted types HOT 1
- Inconsistent behavior with `env.list` HOT 1
- Support all shell parameter expansion
- read_env() with a different file instead of `.env` doesn't work HOT 3
- Update documentation for env.url() to show how to add non-default URLs
- Cannot validate URLs missing the user field component HOT 1
- Incomplete Typing on Env methods HOT 1
- Handle marshmallow DeprecationWarning HOT 2
Recommend Projects
-
React
A declarative, efficient, and flexible JavaScript library for building user interfaces.
-
Vue.js
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
-
Typescript
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
-
TensorFlow
An Open Source Machine Learning Framework for Everyone
-
Django
The Web framework for perfectionists with deadlines.
-
Laravel
A PHP framework for web artisans
-
D3
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
-
Recommend Topics
-
javascript
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
-
web
Some thing interesting about web. New door for the world.
-
server
A server is a program made to process requests and deliver data to clients.
-
Machine learning
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
-
Visualization
Some thing interesting about visualization, use data art
-
Game
Some thing interesting about game, make everyone happy.
Recommend Org
-
Facebook
We are working to build community through open source technology. NB: members must have two-factor auth.
-
Microsoft
Open source projects and samples from Microsoft.
-
Google
Google ❤️ Open Source for everyone.
-
Alibaba
Alibaba Open Source for everyone
-
D3
Data-Driven Documents codes.
-
Tencent
China tencent open source team.
from environs.