Repository: joke2k/django-environ
Pull request #313 (2021-08-31 21:46:59)

Accept Python 3.6 path-like objects for read_env
References:
- joke2k/django-environ#106
- joke2k/django-environ#107


Patch:
diff --git a/CHANGELOG.rst b/CHANGELOG.rst
index 336cd58..1b2f66d 100644
--- a/CHANGELOG.rst
+++ b/CHANGELOG.rst
@@ -12,6 +12,7 @@ Added
   - Python 3.9, 3.10 and pypy 3.7 are now supported
   - Django 3.1 and 3.2 are now supported
   - Added missed classifiers to ``setup.py``
+  - Accept Python 3.6 path-like objects for ``read_env``
 
 Fixed
 +++++
diff --git a/environ/environ.py b/environ/environ.py
index dfc4c1b..b1fb0d3 100644
--- a/environ/environ.py
+++ b/environ/environ.py
@@ -28,6 +28,13 @@ from urllib.parse import (
 
 from .compat import DJANGO_POSTGRES, ImproperlyConfigured, json, REDIS_DRIVER
 
+try:
+    from os import PathLike
+    Openable = (str, PathLike)
+except ImportError:
+    Openable = (str,)
+
+
 logger = logging.getLogger(__name__)
 
 
@@ -735,7 +742,7 @@ class Env:
                 return
 
         try:
-            if isinstance(env_file, str):
+            if isinstance(env_file, Openable):
                 with open(env_file) as f:
                     content = f.read()
             else:
diff --git a/setup.cfg b/setup.cfg
index 3e89f9f..b8a1655 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -1,9 +1,3 @@
-[bumpversion]
-current_version = 0.6.0
-commit = True
-tag = True
-files = setup.py environ/environ.py docs/conf.py
-
 [bdist_wheel]
 universal = 1
 


Test patch:
diff --git a/tests/test_env.py b/tests/test_env.py
index b323c42..c4807de 100644
--- a/tests/test_env.py
+++ b/tests/test_env.py
@@ -275,6 +275,34 @@ class TestFileEnv(TestEnv):
             PATH_VAR=Path(__file__, is_file=True).__root__
         )
 
+    def test_read_env_path_like(self):
+        import pathlib
+        import tempfile
+
+        path_like = (pathlib.Path(tempfile.gettempdir()) / 'test_pathlib.env')
+        try:
+            path_like.unlink()
+        except FileNotFoundError:
+            pass
+
+        assert not path_like.exists()
+
+        env_key = 'SECRET'
+        env_val = 'enigma'
+        env_str = env_key + '=' + env_val
+
+        # open() doesn't take path-like on Python < 3.6
+        try:
+            with open(path_like, 'w', encoding='utf-8') as f:
+                f.write(env_str + '\n')
+        except TypeError:
+            return
+
+        assert path_like.exists()
+        self.env.read_env(path_like)
+        assert env_key in self.env.ENVIRON
+        assert self.env.ENVIRON[env_key] == env_val
+
 
 class TestSubClass(TestEnv):
     def setup_method(self, method):
diff --git a/tests/test_path.py b/tests/test_path.py
index 6859b5e..502cf78 100644
--- a/tests/test_path.py
+++ b/tests/test_path.py
@@ -64,12 +64,28 @@ def test_comparison():
 
 
 def test_sum():
+    """Make sure Path correct handle __add__."""
     assert Path('/') + 'home' == Path('/home')
     assert Path('/') + '/home/public' == Path('/home/public')
+
+
+def test_subtraction():
+    """Make sure Path correct handle __sub__."""
     assert Path('/home/dev/public') - 2 == Path('/home')
     assert Path('/home/dev/public') - 'public' == Path('/home/dev')
 
 
+def test_subtraction_not_int():
+    """Subtraction with an invalid type should raise TypeError."""
+    with pytest.raises(TypeError) as excinfo:
+        Path('/home/dev/') - 'not int'
+    assert str(excinfo.value) == (
+        "unsupported operand type(s) for -: '<class 'environ.environ.Path'>' "
+        "and '<class 'str'>' unless value of <class 'environ.environ.Path'> "
+        "ends with value of <class 'str'>"
+    )
+
+
 def test_required_path():
     root = Path('/home')
     with pytest.raises(ImproperlyConfigured) as excinfo:
@@ -81,16 +97,6 @@ def test_required_path():
     assert "Create required path:" in str(excinfo.value)
 
 
-def test_subtraction_not_int():
-    with pytest.raises(TypeError) as excinfo:
-        Path('/home/dev/') - 'not int'
-    assert str(excinfo.value) == (
-        "unsupported operand type(s) for -: '<class 'environ.environ.Path'>' "
-        "and '<class 'str'>' unless value of <class 'environ.environ.Path'> "
-        "ends with value of <class 'str'>"
-    )
-
-
 def test_complex_manipulation(volume):
     root = Path('/home')
     public = root.path('public')