Repository: joke2k/django-environ
Pull request #391 (2022-06-14 08:57:01)

Deprecate Env.unicode


Patch:
diff --git a/AUTHORS.rst b/AUTHORS.rst
index 6bf3982..0a910fa 100644
--- a/AUTHORS.rst
+++ b/AUTHORS.rst
@@ -13,7 +13,7 @@ The existence of ``django-environ`` would have been impossible without these
 projects:
 
 - `rconradharris/envparse <https://github.com/rconradharris/envparse>`_
-- `jacobian/dj-database-url <https://github.com/jacobian/dj-database-url>`_
+- `jazzband/dj-database-url <https://github.com/jazzband/dj-database-url>`_
 - `migonzalvar/dj-email-url <https://github.com/migonzalvar/dj-email-url>`_
 - `ghickman/django-cache-url <https://github.com/ghickman/django-cache-url>`_
 - `dstufft/dj-search-url <https://github.com/dstufft/dj-search-url>`_
diff --git a/CHANGELOG.rst b/CHANGELOG.rst
index a60149d..5c586d2 100644
--- a/CHANGELOG.rst
+++ b/CHANGELOG.rst
@@ -17,12 +17,10 @@ Added
   `#362 <https://github.com/joke2k/django-environ/issues/362>`_.
 
 
-Fixed
-+++++
-- Fix ``_cast_urlstr`` unquoting
-  `#357 <https://github.com/joke2k/django-environ/issues/357>`_.
-- Fix documentation regarding unsafe characters in URLs
-  `#220 <https://github.com/joke2k/django-environ/issues/220>`_.
+Deprecated
+++++++++++
+- ``Env.unicode()`` is deprecated and will be removed in the next
+  major release. Use ``Env.str()`` instead.
 
 
 Changed
@@ -31,6 +29,14 @@ Changed
   `#360 <https://github.com/joke2k/django-environ/issues/360>`_.
 
 
+Fixed
++++++
+- Fix ``_cast_urlstr`` unquoting
+  `#357 <https://github.com/joke2k/django-environ/issues/357>`_.
+- Fix documentation regarding unsafe characters in URLs
+  `#220 <https://github.com/joke2k/django-environ/issues/220>`_.
+
+
 `v0.8.1`_ - 20-October-2021
 ---------------------------
 Fixed
diff --git a/README.rst b/README.rst
index 6f0976a..256f0a1 100644
--- a/README.rst
+++ b/README.rst
@@ -104,8 +104,8 @@ article.
 
 Using ``django-environ`` you can stop to make a lot of unversioned
 ``settings_*.py`` to configure your app.
-See `cookiecutter-django <https://github.com/pydanny/cookiecutter-django>`_ for
-a concrete example on using with a django project.
+See `cookiecutter-django <https://github.com/cookiecutter/cookiecutter-django>`_
+for a concrete example on using with a django project.
 
 **Feature Support**
 
diff --git a/docs/Makefile b/docs/Makefile
index 2b50c27..887c0bb 100644
--- a/docs/Makefile
+++ b/docs/Makefile
@@ -1,6 +1,6 @@
 # This file is part of the django-environ.
 #
-# Copyright (c) 2021, Serghei Iakovlev <egrep@protonmail.ch>
+# Copyright (c) 2021-2022, Serghei Iakovlev <egrep@protonmail.ch>
 # Copyright (c) 2013-2021, Daniele Faraglia <daniele.faraglia@gmail.com>
 #
 # For the full copyright and license information, please view
diff --git a/docs/api.rst b/docs/api.rst
new file mode 100644
index 0000000..db2db6d
--- /dev/null
+++ b/docs/api.rst
@@ -0,0 +1,45 @@
+=============
+API Reference
+=============
+
+.. currentmodule:: environ
+
+
+The ``__init__`` module
+=======================
+
+.. automodule:: environ
+    :members:
+    :no-undoc-members:
+
+
+The ``compat`` module
+======================
+
+.. automodule:: environ.compat
+    :members:
+    :no-undoc-members:
+
+
+The ``environ`` module
+======================
+
+.. autoclass:: environ.Env
+    :members:
+    :no-undoc-members:
+
+.. autoclass:: environ.FileAwareEnv
+    :members:
+    :no-undoc-members:
+
+.. autoclass:: environ.Path
+    :members:
+    :no-undoc-members:
+
+
+The ``fileaware_mapping`` module
+================================
+
+.. autoclass:: environ.fileaware_mapping.FileAwareMapping
+    :members:
+    :no-undoc-members:
diff --git a/docs/conf.py b/docs/conf.py
index 1753614..1c5390a 100644
--- a/docs/conf.py
+++ b/docs/conf.py
@@ -1,6 +1,6 @@
 # This file is part of the django-environ.
 #
-# Copyright (c) 2021, Serghei Iakovlev <egrep@protonmail.ch>
+# Copyright (c) 2021-2022, Serghei Iakovlev <egrep@protonmail.ch>
 # Copyright (c) 2013-2021, Daniele Faraglia <daniele.faraglia@gmail.com>
 #
 # For the full copyright and license information, please view
@@ -12,9 +12,14 @@
 
 import codecs
 import os
+import sys
 import re
 
 
+PROJECT_DIR = os.path.abspath('..')
+sys.path.insert(0, PROJECT_DIR)
+
+
 def read_file(filepath):
     """Read content from a UTF-8 encoded text file."""
     with codecs.open(filepath, 'rb', 'utf-8') as file_handle:
@@ -23,9 +28,7 @@ def read_file(filepath):
 
 def find_version(meta_file):
     """Extract ``__version__`` from meta_file."""
-    here = os.path.abspath(os.path.dirname(__file__))
-    contents = read_file(os.path.join(here, meta_file))
-
+    contents = read_file(os.path.join(PROJECT_DIR, meta_file))
     meta_match = re.search(
         r"^__version__\s+=\s+['\"]([^'\"]*)['\"]",
         contents,
@@ -56,6 +59,7 @@ extensions = [
     "sphinx.ext.doctest",
     "sphinx.ext.intersphinx",
     "sphinx.ext.todo",
+    "sphinx.ext.viewcode",
     "notfound.extension",
 ]
 
@@ -75,7 +79,7 @@ master_doc = "index"
 
 # The version info
 # The short X.Y version.
-release = find_version('../environ/__init__.py')
+release = find_version('environ/__init__.py')
 version = release.rsplit(u".", 1)[0]
 # The full version, including alpha/beta/rc tags.
 
@@ -85,11 +89,29 @@ exclude_patterns = ["_build"]
 
 # The reST default role (used for this markup: `text`) to use for all
 # documents.
-default_role = "any"
+# default_role = None
 
 # If true, '()' will be appended to :func: etc. cross-reference text.
 add_function_parentheses = True
 
+#
+# -- Options for linkcheck ----------------------------------------------------
+#
+
+linkcheck_ignore = [
+    # We run into GitHub's rate limits.
+    r"https://github.com/.*/(issues|pull)/\d+",
+]
+
+#
+# -- Options for nitpick ----------------------------------------------------
+#
+
+# In nitpick mode (-n), still ignore any of the following "broken" references
+# to non-types.
+nitpick_ignore = [
+]
+
 #
 # -- Options for extlinks ----------------------------------------------------
 #
diff --git a/docs/deprecations.rst b/docs/deprecations.rst
new file mode 100644
index 0000000..7318911
--- /dev/null
+++ b/docs/deprecations.rst
@@ -0,0 +1,12 @@
+============
+Deprecations
+============
+
+Features deprecated in 0.9.0
+============================
+
+Methods
+-------
+
+* The :meth:`.environ.Env.unicode` method is deprecated as it was used
+  for Python 2.x only. Use :meth:`.environ.Env.str` instead.
diff --git a/docs/docutils.conf b/docs/docutils.conf
index 841ea73..4ed0bf5 100644
--- a/docs/docutils.conf
+++ b/docs/docutils.conf
@@ -1,6 +1,6 @@
 # This file is part of the django-environ.
 #
-# Copyright (c) 2021, Serghei Iakovlev <egrep@protonmail.ch>
+# Copyright (c) 2021-2022, Serghei Iakovlev <egrep@protonmail.ch>
 # Copyright (c) 2013-2021, Daniele Faraglia <daniele.faraglia@gmail.com>
 #
 # For the full copyright and license information, please view
diff --git a/docs/getting-started.rst b/docs/getting-started.rst
index 007e92f..e7056fa 100644
--- a/docs/getting-started.rst
+++ b/docs/getting-started.rst
@@ -10,13 +10,14 @@ Requirements
 ------------
 
 * `Django <https://www.djangoproject.com/>`_ >= 1.11
-* `Python <https://www.python.org/>`_ >= 3.4
+* `Python <https://www.python.org/>`_ >= 3.5
 
 Installing django-environ
 _________________________
 
-``django-environ`` is a Python-only package `hosted on PyPI <https://pypi.org/project/django-environ/>`_.
-The recommended installation method is `pip <https://pip.pypa.io/en/stable/>`_-installing into a virtualenv:
+``django-environ`` is a Python-only package `hosted_on_pypi`_.
+The recommended installation method is `pip`_-installing into a
+:mod:`virtualenv <python:venv>`:
 
 .. code-block:: console
 
@@ -26,13 +27,18 @@ The recommended installation method is `pip <https://pip.pypa.io/en/stable/>`_-i
 
    After installing ``django-environ``, no need to add it to ``INSTALLED_APPS``.
 
+
+.. _hosted_on_pypi: https://pypi.org/project/django-environ/
+.. _pip: https://pip.pypa.io/en/stable/
+
+
 Unstable version
 ________________
 
 The master of all the material is the Git repository at https://github.com/joke2k/django-environ.
 So, you can also install the latest unreleased development version directly from the
 ``develop`` branch on GitHub. It is a work-in-progress of a future stable release so the
-experience might be not as smooth.:
+experience might be not as smooth:
 
 .. code-block:: console
 
@@ -52,7 +58,7 @@ it to your system.
 More information about ``pip`` and PyPI can be found here:
 
 * `Install pip <https://pip.pypa.io/en/latest/installing/>`_
-* `Python Packaging User Guide <https://packaging.python.org/>`_
+* `Python Packaging User Guide <https://packaging.python.org/en/latest/>`_
 
 Usage
 =====
@@ -112,10 +118,10 @@ FAQ
 #. **Can django-environ determine the location of .env file automatically?**
 
    ``django-environ`` will try to get and read ``.env`` file from the project
-   root if you haven't specified the path for it when call ``read_env``.
+   root if you haven't specified the path for it when call :meth:`.environ.Env.read_env`.
    However, this is not the recommended way. When it is possible always specify
    the path tho ``.env`` file. Alternatively, you can use a trick with a
-   environment variable pointing to the actual location of .env file.
+   environment variable pointing to the actual location of ``.env`` file.
    For details see ":ref:`multiple-env-files-label`".
 
 #. **What (where) is the root part of the project, is it part of the project where are settings?**
diff --git a/docs/index.rst b/docs/index.rst
index 08aaf75..fca07a9 100644
--- a/docs/index.rst
+++ b/docs/index.rst
@@ -1,3 +1,5 @@
+.. module:: environ
+
 =======================================
 Welcome to django-environ documentation
 =======================================
@@ -38,6 +40,7 @@ Full Table of Contents
    tips
    contributing
    backers
+   api
 
 .. include:: ../README.rst
    :start-after: -project-information-
@@ -47,4 +50,5 @@ Full Table of Contents
    :maxdepth: 1
 
    license
+   deprecations
    changelog
diff --git a/docs/tips.rst b/docs/tips.rst
index 1c7dd6b..3b45ddb 100644
--- a/docs/tips.rst
+++ b/docs/tips.rst
@@ -10,7 +10,7 @@ Docker (swarm) and Kubernetes are two widely used platforms that store their
 secrets in tmpfs inside containers as individual files, providing a secure way
 to be able to share configuration data between containers.
 
-Use ``environ.FileAwareEnv`` rather than ``environ.Env`` to first look for
+Use :class:`.environ.FileAwareEnv` rather than :class:`.environ.Env` to first look for
 environment variables with ``_FILE`` appended. If found, their contents will be
 read from the file system and used instead.
 
@@ -42,8 +42,8 @@ the example ``docker-compose.yml`` for would contain:
 Using unsafe characters in URLs
 ===============================
 
-In order to use unsafe characters you have to encode with ``urllib.parse.quote`` before you set into ``.env`` file.
-Encode only the value (i.e. the password) not the whole url.
+In order to use unsafe characters you have to encode with :py:func:`urllib.parse.quote`
+before you set into ``.env`` file. Encode only the value (i.e. the password) not the whole url.
 
 .. code-block:: shell
 
@@ -292,7 +292,8 @@ while ``./manage.py runserver`` uses ``.env``.
 Using Path objects when reading env
 -----------------------------------
 
-It is possible to use of ``pathlib.Path`` objects when reading environment file from the filesystem:
+It is possible to use of :py:class:`pathlib.Path` objects when reading environment
+file from the filesystem:
 
 .. code-block:: python
 
@@ -319,7 +320,7 @@ Overwriting existing environment values from env files
 
 If you want variables set within your env files to take higher precedence than
 an existing set environment variable, use the ``overwrite=True`` argument of
-``read_env``. For example:
+:meth:`.environ.Env.read_env`. For example:
 
 .. code-block:: python
 
diff --git a/environ/__init__.py b/environ/__init__.py
index e1d99cc..305494c 100644
--- a/environ/__init__.py
+++ b/environ/__init__.py
@@ -1,6 +1,6 @@
 # This file is part of the django-environ.
 #
-# Copyright (c) 2021, Serghei Iakovlev <egrep@protonmail.ch>
+# Copyright (c) 2021-2022, Serghei Iakovlev <egrep@protonmail.ch>
 # Copyright (c) 2013-2021, Daniele Faraglia <daniele.faraglia@gmail.com>
 #
 # For the full copyright and license information, please view
@@ -13,19 +13,19 @@ package info used by various functions within django-environ.
 
 Misc variables:
 
-    __copyright__
-    __version__
-    __license__
-    __author__
-    __author_email__
-    __maintainer__
-    __maintainer_email__
-    __url__
-    __description__
-
-Refer to the `documentation <https://django-environ.readthedocs.org/>`_
+* ``__copyright__``: The copyright notice of the package.
+* ``__version__``: The version of the package.
+* ``__license__``: The license of the package.
+* ``__author__``: The author of the package.
+* ``__author_email__``: The email of the author of the package.
+* ``__maintainer__``: The maintainer of the package.
+* ``__maintainer_email__``: The email of the maintainer of the package.
+* ``__url__``: The URL of the package.
+* ``__description__``: The description of the package.
+
+Refer to the `documentation <https://django-environ.readthedocs.io/en/latest/>`_
 for details on the use of this package.
-"""
+"""  # noqa: E501
 
 from .environ import *
 
diff --git a/environ/compat.py b/environ/compat.py
index 8c259f8..a4ae366 100644
--- a/environ/compat.py
+++ b/environ/compat.py
@@ -1,6 +1,6 @@
 # This file is part of the django-environ.
 #
-# Copyright (c) 2021, Serghei Iakovlev <egrep@protonmail.ch>
+# Copyright (c) 2021-2022, Serghei Iakovlev <egrep@protonmail.ch>
 # Copyright (c) 2013-2021, Daniele Faraglia <daniele.faraglia@gmail.com>
 #
 # For the full copyright and license information, please view
@@ -39,8 +39,8 @@ else:
     REDIS_DRIVER = 'django_redis.cache.RedisCache'
 
 
-# back compatibility for pymemcache
 def choose_pymemcache_driver():
+    """Backward compatibility for pymemcache."""
     old_django = DJANGO_VERSION is not None and DJANGO_VERSION < (3, 2)
     if old_django or not find_loader('pymemcache'):
         # The original backend choice for the 'pymemcache' scheme is
diff --git a/environ/environ.py b/environ/environ.py
index b69c7b9..a222a47 100644
--- a/environ/environ.py
+++ b/environ/environ.py
@@ -1,6 +1,6 @@
 # This file is part of the django-environ.
 #
-# Copyright (c) 2021, Serghei Iakovlev <egrep@protonmail.ch>
+# Copyright (c) 2021-2022, Serghei Iakovlev <egrep@protonmail.ch>
 # Copyright (c) 2013-2021, Daniele Faraglia <daniele.faraglia@gmail.com>
 #
 # For the full copyright and license information, please view
@@ -200,6 +200,15 @@ class Env:
         """Helper for python2
         :rtype: unicode
         """
+        warnings.warn(
+            '`%s.unicode` is deprecated, use `%s.str` instead' % (
+                self.__class__.__name__,
+                self.__class__.__name__,
+            ),
+            DeprecationWarning,
+            stacklevel=2
+        )
+
         return self.get_value(var, cast=str, default=default)
 
     def bytes(self, var, default=NOTSET, encoding='utf8'):
@@ -263,7 +272,7 @@ class Env:
 
     def url(self, var, default=NOTSET):
         """
-        :rtype: urlparse.ParseResult
+        :rtype: urllib.parse.ParseResult
         """
         return self.get_value(
             var,
@@ -780,7 +789,7 @@ class Env:
 
     @classmethod
     def read_env(cls, env_file=None, overwrite=False, **overrides):
-        """Read a .env file into os.environ.
+        r"""Read a .env file into os.environ.
 
         If not given a path to a dotenv path, does filthy magic stack
         backtracking to find the dotenv in the same directory as the file that
@@ -794,12 +803,12 @@ class Env:
         - https://wellfire.co/learn/easier-12-factor-django
         - https://gist.github.com/bennylope/2999704
 
-        :param env_file: The path to the `.env` file your application should
+        :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
             the Django settings module from the Django project root.
         :param overwrite: ``overwrite=True`` will force an overwrite of
             existing environment variables.
-        :param **overrides: Any additional keyword arguments provided directly
+        :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.
         """
@@ -902,13 +911,12 @@ class Path:
         return self.__class__(self.__root__, *paths, **kwargs)
 
     def file(self, name, *args, **kwargs):
-        """Open a file.
-
-        :param name: Filename appended to self.root
-        :param args: passed to open()
-        :param kwargs: passed to open()
+        r"""Open a file.
 
-        :rtype: file
+        :param str name: Filename appended to :py:attr:`~root`
+        :param \*args: ``*args`` passed to :py:func:`open`
+        :param \**kwargs: ``**kwargs`` passed to :py:func:`open`
+        :rtype: typing.IO[typing.Any]
         """
         return open(self(name), *args, **kwargs)
 
diff --git a/environ/fileaware_mapping.py b/environ/fileaware_mapping.py
index 706b92c..6da95f5 100644
--- a/environ/fileaware_mapping.py
+++ b/environ/fileaware_mapping.py
@@ -1,6 +1,6 @@
 # This file is part of the django-environ.
 #
-# Copyright (c) 2021, Serghei Iakovlev <egrep@protonmail.ch>
+# Copyright (c) 2021-2022, Serghei Iakovlev <egrep@protonmail.ch>
 # Copyright (c) 2013-2021, Daniele Faraglia <daniele.faraglia@gmail.com>
 #
 # For the full copyright and license information, please view
@@ -14,14 +14,14 @@ from collections.abc import MutableMapping
 
 class FileAwareMapping(MutableMapping):
     """
-    A mapping that wraps os.environ, first checking for the existance of a key
+    A mapping that wraps os.environ, first checking for the existence of a key
     appended with ``_FILE`` whenever reading a value. If a matching file key is
     found then the value is instead read from the file system at this location.
 
     By default, values read from the file system are cached so future lookups
     do not hit the disk again.
 
-    A ``_FILE`` key has higher precidence than a value is set directly in the
+    A ``_FILE`` key has higher precedence than a value is set directly in the
     environment, and an exception is raised if the file can not be found.
     """
 


Test patch:
diff --git a/tests/test_env.py b/tests/test_env.py
index cfde92e..b5c1e39 100644
--- a/tests/test_env.py
+++ b/tests/test_env.py
@@ -8,6 +8,7 @@
 
 import os
 from urllib.parse import quote
+from warnings import catch_warnings
 
 import pytest
 
@@ -70,6 +71,21 @@ class TestEnv:
             assert self.env(var) == val
         assert self.env.str(var, multiline=multiline) == val
 
+    def test_unicode(self, recwarn):
+        actual = self.env.unicode('CYRILLIC_VAR', default='фуубар')
+        expected = self.env.str('CYRILLIC_VAR', default='фуубар')
+
+        assert actual == expected
+        assert len(recwarn) == 1
+        w = recwarn.pop(DeprecationWarning)
+        assert issubclass(w.category, DeprecationWarning)
+        assert str(w.message) == '`%s.unicode` is deprecated, use `%s.str` instead' %(
+            self.env.__class__.__name__,
+            self.env.__class__.__name__,
+        )
+        assert w.filename
+        assert w.lineno
+
     @pytest.mark.parametrize(
         'var,val,default',
         [