Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ jobs:
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install tox tox-gh-actions setuptools
pip install tox tox-gh-actions build

- name: Check MANIFEST.in for completeness
run: tox -e manifest
Expand Down
7 changes: 3 additions & 4 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -71,11 +71,10 @@ jobs:
python -m pip install --upgrade pip
python -m pip install tox tox-gh-actions setuptools

- name: Setuptools self-test
- name: Package metadata validation
run: |
python setup.py --fullname
python setup.py --long-description
python setup.py --classifiers
python -m pip install build
python -c "import tomllib; print('pyproject.toml is valid')" || python -c "import tomli; print('pyproject.toml is valid')"

- name: Run unit tests with coverage
run: tox
Expand Down
7 changes: 6 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# This file is part of the django-environ.
#
# Copyright (c) 2021-2024, Serghei Iakovlev <[email protected]>
# Copyright (c) 2021-2025, Serghei Iakovlev <[email protected]>
# Copyright (c) 2013-2021, Daniele Faraglia <[email protected]>
#
# For the full copyright and license information, please view
Expand All @@ -18,6 +18,8 @@
/.tox
/build
/dist
/dist_*
/test_*
/*.egg-info
/htmlcov
/docs/_build
Expand All @@ -29,3 +31,6 @@ __pycache__
# Ignore codecoverage stuff.
.coverage*
coverage.xml

# Generated files
README_COMBINED.rst
2 changes: 1 addition & 1 deletion BACKERS.rst
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ Thank you to all our backers!

|ocbackerimage|

.. |ocsponsor0| image:: https://opencollective.com/django-environ/sponsor/0/avatar.svg
.. |ocsponsor0| image:: https://images.opencollective.com/static/images/become_sponsor.svg
:target: https://opencollective.com/triplebyte
:alt: Sponsor
.. |ocsponsor1| image:: https://images.opencollective.com/static/images/become_sponsor.svg
Expand Down
8 changes: 7 additions & 1 deletion MANIFEST.in
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
# or remove some set of files from the sdist.

# Include all files matching any of the listed patterns.
include *.rst LICENSE.txt *.yml
include *.rst LICENSE.txt *.yml setup_helpers.py
graft .github

# The contents of the directory tree tests will first be added to the sdist.
Expand All @@ -24,6 +24,12 @@ include tox.ini
# from the sdist.
global-exclude *.py[cod]

# Exclude build artifacts and temporary files
global-exclude build/*
global-exclude dist/*
global-exclude *.egg-info/*
global-exclude tmp_rovodev_*

# Documentation
include docs/docutils.conf docs/Makefile
recursive-include docs *.png
Expand Down
61 changes: 33 additions & 28 deletions README.rst
Original file line number Diff line number Diff line change
@@ -1,29 +1,34 @@
.. raw:: html

<h1 align="center">django-environ</h1>
<p align="center">
<a href="https://pypi.python.org/pypi/django-environ">
<img src="https://img.shields.io/pypi/v/django-environ.svg" alt="Latest version released on PyPi" />
</a>
<a href="https://coveralls.io/github/joke2k/django-environ">
<img src="https://coveralls.io/repos/github/joke2k/django-environ/badge.svg" alt="Coverage Status" />
</a>
<a href="https://github.com/joke2k/django-environ/actions?workflow=CI">
<img src="https://github.com/joke2k/django-environ/workflows/CI/badge.svg?branch=develop" alt="CI Status" />
</a>
<a href="https://opencollective.com/django-environ">
<img src="https://opencollective.com/django-environ/sponsors/badge.svg" alt="Sponsors on Open Collective" />
</a>
<a href="https://opencollective.com/django-environ">
<img src="https://opencollective.com/django-environ/backers/badge.svg" alt="Backers on Open Collective" />
</a>
<a href="https://saythanks.io/to/joke2k">
<img src="https://img.shields.io/badge/Say%20Thanks-!-1EAEDB.svg" alt="Say Thanks!" />
</a>
<a href="https://raw.githubusercontent.com/joke2k/django-environ/main/LICENSE.txt">
<img src="https://img.shields.io/badge/license-MIT-blue.svg" alt="Package license" />
</a>
</p>
================
django-environ
================

.. image:: https://img.shields.io/pypi/v/django-environ.svg
:target: https://pypi.python.org/pypi/django-environ
:alt: Latest version released on PyPi

.. image:: https://coveralls.io/repos/github/joke2k/django-environ/badge.svg
:target: https://coveralls.io/github/joke2k/django-environ
:alt: Coverage Status

.. image:: https://github.com/joke2k/django-environ/workflows/CI/badge.svg?branch=develop
:target: https://github.com/joke2k/django-environ/actions?workflow=CI
:alt: CI Status

.. image:: https://opencollective.com/django-environ/sponsors/badge.svg
:target: https://opencollective.com/django-environ
:alt: Sponsors on Open Collective

.. image:: https://opencollective.com/django-environ/backers/badge.svg
:target: https://opencollective.com/django-environ
:alt: Backers on Open Collective

.. image:: https://img.shields.io/badge/Say%20Thanks-!-1EAEDB.svg
:target: https://saythanks.io/to/joke2k
:alt: Say Thanks!

.. image:: https://img.shields.io/badge/license-MIT-blue.svg
:target: https://raw.githubusercontent.com/joke2k/django-environ/main/LICENSE.txt
:alt: Package license

.. -teaser-begin-

Expand Down Expand Up @@ -92,14 +97,14 @@ environment variables obtained from an environment file and provided by the OS:

The idea of this package is to unify a lot of packages that make the same stuff:
Take a string from ``os.environ``, parse and cast it to some of useful python
typed variables. To do that and to use the `12factor <https://www.12factor.net/>`_
typed variables. To do that and to use the `12-factor <https://www.12factor.net/>`_
approach, some connection strings are expressed as url, so this package can parse
it and return a ``urllib.parse.ParseResult``. These strings from ``os.environ``
are loaded from a ``.env`` file and filled in ``os.environ`` with ``setdefault``
method, to avoid to overwrite the real environ.
A similar approach is used in
`Two Scoops of Django <https://web.archive.org/web/20240121133956/https://www.feldroy.com/books/two-scoops-of-django-3-x>`_
book and explained in `12factor-django <https://wellfire.co/learn/easier-12-factor-django>`_
book and explained in `12-factor-django <https://web.archive.org/web/20250522195250/https://wellfire.co/learn/easier-12-factor-django/>`_
article.


Expand Down
9 changes: 0 additions & 9 deletions docs/api.rst
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,6 @@ API Reference
.. currentmodule:: environ


The ``__init__`` module
=======================

.. automodule:: environ
:members:
:special-members:
:no-undoc-members:


The ``compat`` module
======================

Expand Down
2 changes: 2 additions & 0 deletions docs/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,8 @@ def find_version(meta_file):
r"https://github.com/joke2k/django-environ/compare/.*",
]

linkcheck_timeout = 60 # Default of 30 seconds failed in CI

#
# -- Options for nitpick -----------------------------------------------------
#
Expand Down
12 changes: 10 additions & 2 deletions environ/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,16 @@
for details on the use of this package.
""" # noqa: E501

from .environ import *
from .environ import Env, FileAwareEnv, NoValue, Path
from .fileaware_mapping import FileAwareMapping

__all__ = [
'Env',
'FileAwareEnv',
'FileAwareMapping',
'NoValue',
'Path',
]

__copyright__ = 'Copyright (C) 2013-2023 Daniele Faraglia'
"""The copyright notice of the package."""
Expand All @@ -43,5 +51,5 @@
"""The URL of the package."""

# pylint: disable=line-too-long
__description__ = 'A package that allows you to utilize 12factor inspired environment variables to configure your Django application.' # noqa: E501
__description__ = 'A package that allows you to utilize 12-factor inspired environment variables to configure your Django application.' # noqa: E501
"""The description of the package."""
12 changes: 12 additions & 0 deletions environ/compat.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,18 @@

from importlib.util import find_spec

__all__ = [
'json',
'DJANGO_VERSION',
'ImproperlyConfigured',
'REDIS_DRIVER',
'DJANGO_POSTGRES',
'PYMEMCACHE_DRIVER',
'choose_rediscache_driver',
'choose_postgres_driver',
'choose_pymemcache_driver',
]

if find_spec('simplejson'):
import simplejson as json
else:
Expand Down
26 changes: 20 additions & 6 deletions environ/environ.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
# the LICENSE.txt file that was distributed with this source code.

"""
Django-environ allows you to utilize 12factor inspired environment
Django-environ allows you to utilize 12-factor inspired environment
variables to configure your Django application.
"""

Expand Down Expand Up @@ -566,21 +566,34 @@ def db_url_config(cls, url, engine=None):
path += f':{url.port}'

user_host = url.netloc.rsplit('@', 1)
if url.scheme in cls.POSTGRES_FAMILY and ',' in user_host[-1]:
if (url.scheme in cls.POSTGRES_FAMILY and
(',' in user_host[-1] or '%2C' in user_host[-1])):
# Parsing postgres cluster dsn
# Handle both raw and URL-encoded commas
host_part = unquote_plus(user_host[-1])
hinfo = list(
itertools.zip_longest(
*(
host.rsplit(':', 1)
for host in user_host[-1].split(',')
for host in host_part.split(',')
)
)
)
hostname = ','.join(hinfo[0])
port = ','.join(filter(None, hinfo[1])) if len(hinfo) == 2 else ''
else:
hostname = url.hostname
port = url.port
# Only access url.port if we're not dealing with a cluster URL
# that might have been URL-encoded
try:
port = url.port
except ValueError:
# Handle case where port parsing fails due to URL encoding
# Extract port manually from netloc
if ':' in user_host[-1]:
port = user_host[-1].split(':')[-1]
else:
port = ''

# Update with environment configuration.
config.update({
Expand Down Expand Up @@ -924,7 +937,8 @@ def read_env(cls, env_file=None, overwrite=False, parse_comments=False,

Refs:

* https://wellfire.co/learn/easier-12-factor-django
* 12-factor Django guide (archived):
https://web.archive.org/web/20250522195250/https://wellfire.co/learn/easier-12-factor-django/

:param env_file: The path to the ``.env`` file your application should
use. If a path is not provided, `read_env` will attempt to import
Expand All @@ -937,7 +951,7 @@ def read_env(cls, env_file=None, overwrite=False, parse_comments=False,
:param \**overrides: Any additional keyword arguments provided directly
to read_env will be added to the environment. If the key matches an
existing environment variable, the value will be overridden.
"""
""" # NoQA: E501
if env_file is None:
# pylint: disable=protected-access
frame = sys._getframe()
Expand Down
85 changes: 85 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
[build-system]
requires = ["setuptools>=61.0", "wheel"]
build-backend = "setuptools.build_meta"

[project]
name = "django-environ"
dynamic = ["version", "readme"]
description = "A package that allows you to utilize 12-factor inspired environment variables to configure your Django application."
license = "MIT"
authors = [
{name = "Daniele Faraglia", email = "[email protected]"}
]
maintainers = [
{name = "Serghei Iakovlev", email = "[email protected]"}
]
keywords = ["environment", "django", "variables", "12factor"]
classifiers = [
"Development Status :: 5 - Production/Stable",
"Framework :: Django",
"Framework :: Django :: 2.2",
"Framework :: Django :: 3.0",
"Framework :: Django :: 3.1",
"Framework :: Django :: 3.2",
"Framework :: Django :: 4.0",
"Framework :: Django :: 4.1",
"Framework :: Django :: 4.2",
"Framework :: Django :: 5.0",
"Framework :: Django :: 5.1",
"Operating System :: OS Independent",
"Intended Audience :: Developers",
"Natural Language :: English",
"Programming Language :: Python",
"Programming Language :: Python :: 3",
"Programming Language :: Python :: 3.9",
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12",
"Programming Language :: Python :: 3.13",
"Programming Language :: Python :: Implementation :: CPython",
"Programming Language :: Python :: Implementation :: PyPy",
"Topic :: Software Development :: Libraries :: Python Modules",
"Topic :: Utilities",
]
requires-python = ">=3.9"
dependencies = []

[project.optional-dependencies]
testing = [
"coverage[toml]>=5.0a4",
"pytest>=4.6.11",
"setuptools>=71.0.0",
]
docs = [
"furo>=2024.8.6",
"sphinx>=5.0",
"sphinx-notfound-page",
]
develop = [
"coverage[toml]>=5.0a4",
"pytest>=4.6.11",
"setuptools>=71.0.0",
"furo>=2024.8.6",
"sphinx>=5.0",
"sphinx-notfound-page",
]

[tool.setuptools]
platforms = ["any"]
include-package-data = true
zip-safe = false

[tool.setuptools.packages.find]
exclude = ["tests.*", "tests", "build.*", "build", "docs.*", "docs"]

[project.urls]
Documentation = "https://django-environ.readthedocs.org"
Funding = "https://opencollective.com/django-environ"
"Say Thanks!" = "https://saythanks.io/to/joke2k"
Changelog = "https://django-environ.readthedocs.io/en/latest/changelog.html"
"Bug Tracker" = "https://github.com/joke2k/django-environ/issues"
"Source Code" = "https://github.com/joke2k/django-environ"

[tool.setuptools.dynamic]
version = {attr = "environ.__version__"}
readme = {file = "README_COMBINED.rst", content-type = "text/x-rst"}
3 changes: 0 additions & 3 deletions setup.cfg

This file was deleted.

Loading