diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 342d5ce8..9e135e19 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -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 diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 0d80bfe7..a1b33027 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -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 diff --git a/.gitignore b/.gitignore index ec6473a6..abb977f4 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,6 @@ # This file is part of the django-environ. # -# Copyright (c) 2021-2024, Serghei Iakovlev +# Copyright (c) 2021-2025, Serghei Iakovlev # Copyright (c) 2013-2021, Daniele Faraglia # # For the full copyright and license information, please view @@ -18,6 +18,8 @@ /.tox /build /dist +/dist_* +/test_* /*.egg-info /htmlcov /docs/_build @@ -29,3 +31,6 @@ __pycache__ # Ignore codecoverage stuff. .coverage* coverage.xml + +# Generated files +README_COMBINED.rst diff --git a/BACKERS.rst b/BACKERS.rst index dc014446..779ae88f 100644 --- a/BACKERS.rst +++ b/BACKERS.rst @@ -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 diff --git a/MANIFEST.in b/MANIFEST.in index 694e938d..b3ec8e35 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -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. @@ -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 diff --git a/README.rst b/README.rst index ff8e0125..ffb627f7 100644 --- a/README.rst +++ b/README.rst @@ -1,29 +1,34 @@ -.. raw:: html - -

django-environ

-

- - Latest version released on PyPi - - - Coverage Status - - - CI Status - - - Sponsors on Open Collective - - - Backers on Open Collective - - - Say Thanks! - - - Package license - -

+================ +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- @@ -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 `_ +typed variables. To do that and to use the `12-factor `_ 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 `_ -book and explained in `12factor-django `_ +book and explained in `12-factor-django `_ article. diff --git a/docs/api.rst b/docs/api.rst index e1556abc..040865ff 100644 --- a/docs/api.rst +++ b/docs/api.rst @@ -5,15 +5,6 @@ API Reference .. currentmodule:: environ -The ``__init__`` module -======================= - -.. automodule:: environ - :members: - :special-members: - :no-undoc-members: - - The ``compat`` module ====================== diff --git a/docs/conf.py b/docs/conf.py index 0e91d43c..54296328 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -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 ----------------------------------------------------- # diff --git a/environ/__init__.py b/environ/__init__.py index 8d29d2f1..08e7c69b 100644 --- a/environ/__init__.py +++ b/environ/__init__.py @@ -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.""" @@ -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.""" diff --git a/environ/compat.py b/environ/compat.py index 55953fe5..31622777 100644 --- a/environ/compat.py +++ b/environ/compat.py @@ -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: diff --git a/environ/environ.py b/environ/environ.py index 5536f2c7..b63a9573 100644 --- a/environ/environ.py +++ b/environ/environ.py @@ -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. """ @@ -566,13 +566,16 @@ 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(',') ) ) ) @@ -580,7 +583,17 @@ def db_url_config(cls, url, engine=None): 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({ @@ -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 @@ -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() diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 00000000..7a06dd03 --- /dev/null +++ b/pyproject.toml @@ -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 = "daniele.faraglia@gmail.com"} +] +maintainers = [ + {name = "Serghei Iakovlev", email = "oss@serghei.pl"} +] +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"} diff --git a/setup.cfg b/setup.cfg deleted file mode 100644 index b8a1655f..00000000 --- a/setup.cfg +++ /dev/null @@ -1,3 +0,0 @@ -[bdist_wheel] -universal = 1 - diff --git a/setup.py b/setup.py index e9272177..a028f945 100644 --- a/setup.py +++ b/setup.py @@ -1,236 +1,30 @@ -#!/usr/bin/env python -# -# This file is part of the django-environ. -# -# Copyright (c) 2021-2024, Serghei Iakovlev -# Copyright (c) 2013-2021, Daniele Faraglia -# -# For the full copyright and license information, please view -# the LICENSE.txt file that was distributed with this source code. +#!/usr/bin/env python3 +""" +Deprecated setup.py for django-environ. -import codecs -import re -from os import path +This file is deprecated and will be removed in a future release. +Please use `python -m build` instead of `python setup.py` commands. -from setuptools import find_packages, setup +For more information, see: +https://blog.ganssle.io/articles/2021/10/setup-py-deprecated.html +""" -# Use this code block for future deprecations of Python version: -# -# import warnings -# import sys -# -# if sys.version_info < (3, 6): -# warnings.warn( -# "Support of Python < 3.6 is deprecated" -# "and will be removed in a future release.", -# DeprecationWarning -# ) +import warnings +from setuptools import setup -def read_file(filepath): - """Read content from a UTF-8 encoded text file.""" - with codecs.open(filepath, 'rb', 'utf-8') as file_handle: - return file_handle.read() - - -PKG_NAME = 'django-environ' -PKG_DIR = path.abspath(path.dirname(__file__)) -META_PATH = path.join(PKG_DIR, 'environ', '__init__.py') -META_CONTENTS = read_file(META_PATH) - - -def load_long_description(): - """Load long description from file README.rst.""" - def changes(): - changelog = path.join(PKG_DIR, 'CHANGELOG.rst') - pattern = ( - r'(`(v\d+.\d+.\d+)`_( - \d{1,2}-\w+-\d{4}\r?\n-+\r?\n.*?))' - r'\r?\n\r?\n\r?\n`v\d+.\d+.\d+`_' - ) - result = re.search(pattern, read_file(changelog), re.S) - - return result.group(2) + result.group(3) if result else '' - - try: - title = PKG_NAME - head = '=' * (len(title)) - - contents = ( - head, - format(title.strip(' .')), - head, - read_file(path.join(PKG_DIR, 'README.rst')).split( - '.. -teaser-begin-' - )[1], - '', - read_file(path.join(PKG_DIR, 'CONTRIBUTING.rst')), - '', - 'Release Information', - '===================\n', - changes(), - '', - '`Full changelog <{}/en/latest/changelog.html>`_.'.format( - find_meta('url') - ), - '', - read_file(path.join(PKG_DIR, 'SECURITY.rst')), - '', - read_file(path.join(PKG_DIR, 'AUTHORS.rst')), - ) - - return '\n'.join(contents) - except (RuntimeError, FileNotFoundError) as read_error: - message = 'Long description could not be read from README.rst' - raise RuntimeError('%s: %s' % (message, read_error)) from read_error - - -def is_canonical_version(version): - """Check if a version string is in the canonical format of PEP 440.""" - pattern = ( - r'^([1-9][0-9]*!)?(0|[1-9][0-9]*)(\.(0|[1-9][0-9]*))' - r'*((a|b|rc)(0|[1-9][0-9]*))?(\.post(0|[1-9][0-9]*))' - r'?(\.dev(0|[1-9][0-9]*))?$') - return re.match(pattern, version) is not None - - -def find_meta(meta): - """Extract __*meta*__ from META_CONTENTS.""" - meta_match = re.search( - r"^__{meta}__\s+=\s+['\"]([^'\"]*)['\"]".format(meta=meta), - META_CONTENTS, - re.M - ) - - if meta_match: - return meta_match.group(1) - raise RuntimeError( - 'Unable to find __%s__ string in package meta file' % meta) - - -def get_version_string(): - """Return package version as listed in `__version__` in meta file.""" - # Parse version string - version_string = find_meta('version') - - # Check validity - if not is_canonical_version(version_string): - message = ( - 'The detected version string "{}" is not in canonical ' - 'format as defined in PEP 440.'.format(version_string)) - raise ValueError(message) - - return version_string - - -# What does this project relate to? -KEYWORDS = [ - 'environment', - 'django', - 'variables', - '12factor', -] - -# Classifiers: available ones listed at https://pypi.org/classifiers -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', - - 'License :: OSI Approved :: MIT License', -] - -# Dependencies that are downloaded by pip on installation and why. -INSTALL_REQUIRES = [] - -DEPENDENCY_LINKS = [] - -# List additional groups of dependencies here (e.g. testing dependencies). -# You can install these using the following syntax, for example: -# -# $ pip install -e .[testing,docs,develop] -# -EXTRAS_REQUIRE = { - # Dependencies that are required to run tests - 'testing': [ - 'coverage[toml]>=5.0a4', # Code coverage measurement for Python - 'pytest>=4.6.11', # Our tests framework - 'setuptools>=71.0.0', # Needed as a dependency for some tests - ], - # Dependencies that are required to build documentation - 'docs': [ - 'furo>=2024.8.6', # Sphinx documentation theme - 'sphinx>=5.0', # Python documentation generator - 'sphinx-notfound-page', # Create a custom 404 page - ], -} - -# Dependencies that are required to develop package -DEVELOP_REQUIRE = [] - -# Dependencies that are required to develop package -EXTRAS_REQUIRE['develop'] = \ - DEVELOP_REQUIRE + EXTRAS_REQUIRE['testing'] + EXTRAS_REQUIRE['docs'] - -# Project's URLs -PROJECT_URLS = { - 'Documentation': find_meta('url'), - 'Funding': 'https://opencollective.com/django-environ', - 'Say Thanks!': 'https://saythanks.io/to/joke2k', - 'Changelog': '{}/en/latest/changelog.html'.format(find_meta('url')), - 'Bug Tracker': 'https://github.com/joke2k/django-environ/issues', - 'Source Code': 'https://github.com/joke2k/django-environ', -} +# Issue deprecation warning +warnings.warn( + "setup.py install is deprecated. " + "Use `python -m pip install .` instead. " + "Direct setup.py usage will be removed by 2025-Oct-31. See https://setuptools.pypa.io/en/stable/history.html#v80-1-0" + "See https://packaging.python.org/en/latest/discussions/setup-py-deprecated/", + DeprecationWarning, + stacklevel=2 +) if __name__ == '__main__': - setup( - name=PKG_NAME, - version=get_version_string(), - author=find_meta('author'), - author_email=find_meta('author_email'), - maintainer=find_meta('maintainer'), - maintainer_email=find_meta('maintainer_email'), - license=find_meta('license'), - description=find_meta('description'), - long_description=load_long_description(), - long_description_content_type='text/x-rst', - keywords=KEYWORDS, - url=find_meta('url'), - project_urls=PROJECT_URLS, - classifiers=CLASSIFIERS, - packages=find_packages(exclude=['tests.*', 'tests']), - platforms=['any'], - include_package_data=True, - zip_safe=False, - python_requires='>=3.9,<4', - install_requires=INSTALL_REQUIRES, - dependency_links=DEPENDENCY_LINKS, - extras_require=EXTRAS_REQUIRE, - ) + # All configuration is now in pyproject.toml + # This is just for backward compatibility + setup() diff --git a/setup_helpers.py b/setup_helpers.py new file mode 100644 index 00000000..d24fa743 --- /dev/null +++ b/setup_helpers.py @@ -0,0 +1,95 @@ +#!/usr/bin/env python +""" +Setup helpers for django-environ package. +Extracted from old_setup.py to generate combined README. +""" + +import codecs +import re +from os import path + + +def read_file(filepath): + """Read content from a UTF-8 encoded text file.""" + with codecs.open(filepath, 'rb', 'utf-8') as file_handle: + return file_handle.read() + + +def find_meta(meta): + """Extract __*meta*__ from environ/__init__.py.""" + PKG_DIR = path.abspath(path.dirname(__file__)) + META_PATH = path.join(PKG_DIR, 'environ', '__init__.py') + META_CONTENTS = read_file(META_PATH) + + meta_match = re.search( + r"^__{meta}__\s+=\s+['\"]([^'\"]*)['\"]".format(meta=meta), + META_CONTENTS, + re.M + ) + + if meta_match: + return meta_match.group(1) + raise RuntimeError( + 'Unable to find __%s__ string in package meta file' % meta) + + +def load_long_description(): + """Load long description from file README.rst. Copied from old_setup.py.""" + PKG_DIR = path.abspath(path.dirname(__file__)) + PKG_NAME = 'django-environ' + + def changes(): + changelog = path.join(PKG_DIR, 'CHANGELOG.rst') + pattern = ( + r'(`(v\d+.\d+.\d+)`_( - \d{1,2}-\w+-\d{4}\r?\n-+\r?\n.*?))' + r'\r?\n\r?\n\r?\n`v\d+.\d+.\d+`_' + ) + result = re.search(pattern, read_file(changelog), re.S) + + return result.group(2) + result.group(3) if result else '' + + try: + title = PKG_NAME + head = '=' * (len(title)) + + contents = ( + head, + format(title.strip(' .')), + head, + read_file(path.join(PKG_DIR, 'README.rst')).split( + '.. -teaser-begin-' + )[1], + '', + read_file(path.join(PKG_DIR, 'CONTRIBUTING.rst')), + '', + 'Release Information', + '===================\n', + changes(), + '', + '`Full changelog <{}/en/latest/changelog.html>`_.'.format( + find_meta('url') + ), + '', + read_file(path.join(PKG_DIR, 'SECURITY.rst')), + '', + read_file(path.join(PKG_DIR, 'AUTHORS.rst')), + ) + + return '\n'.join(contents) + except (RuntimeError, FileNotFoundError) as read_error: + message = 'Long description could not be read from README.rst' + raise RuntimeError('%s: %s' % (message, read_error)) from read_error + + +# Generate the combined README file when imported during build +PKG_DIR = path.abspath(path.dirname(__file__)) +combined_path = path.join(PKG_DIR, 'README_COMBINED.rst') + +try: + long_description = load_long_description() + with codecs.open(combined_path, 'w', 'utf-8') as f: + f.write(long_description) +except Exception: + # Fallback to basic README if generation fails + import shutil + shutil.copy('README.rst', combined_path) \ No newline at end of file diff --git a/tests/test_cache.py b/tests/test_cache.py index 58e57e0b..29d5ad9c 100644 --- a/tests/test_cache.py +++ b/tests/test_cache.py @@ -13,9 +13,9 @@ import environ.compat from environ import Env from environ.compat import ( - ImproperlyConfigured, PYMEMCACHE_DRIVER, REDIS_DRIVER, + ImproperlyConfigured, ) @@ -134,6 +134,7 @@ def test_rediscache_compat(django_version, django_redis_installed): else: assert driver == redis_cache + def test_redis_parsing(): url = ('rediscache://127.0.0.1:6379/1?client_class=' 'django_redis.client.DefaultClient&password=secret') diff --git a/tests/test_db.py b/tests/test_db.py index e26c5357..253d435c 100644 --- a/tests/test_db.py +++ b/tests/test_db.py @@ -141,7 +141,7 @@ 'enigma', 'secret', 12345), - # mysql://user:password@host:port/dbname + # mysql://user:password@host:port/dbname ('mysql://enigma:><{~!@#$%^&*}[]@example.com:1234/dbname', 'django.db.backends.mysql', 'dbname', diff --git a/tests/test_schema.py b/tests/test_schema.py index 45f0e983..a3393b10 100644 --- a/tests/test_schema.py +++ b/tests/test_schema.py @@ -9,6 +9,7 @@ import os from environ import Env + from .fixtures import FakeEnv _old_environ = None @@ -28,6 +29,7 @@ def teardown_module(): assert _old_environ is not None os.environ = _old_environ + _old_environ = None def test_schema(): diff --git a/tox.ini b/tox.ini index a0b8dc28..574ab41d 100644 --- a/tox.ini +++ b/tox.ini @@ -71,7 +71,7 @@ commands_pre = python -m pip install --upgrade pip python -m pip install . commands = - flake8 environ setup.py + flake8 environ pylint \ --logging-format-style=old \ --good-names-rgxs=m[0-9],f,v \ @@ -144,13 +144,13 @@ commands = check-manifest -v description = Build and test package distribution skip_install = true deps = + build twine check-wheel-contents commands_pre = python -m pip install -U pip setuptools wheel - python setup.py bdist_wheel -d {envtmpdir}/build - python setup.py sdist -d {envtmpdir}/build commands = + python -m build --outdir {envtmpdir}/build twine check {envtmpdir}/build/* check-wheel-contents {envtmpdir}/build