Repository: sloria/environs

Issue:
Feature Request: Add Support for Prefix in Env Constructor
Hi, 

I have a use case where all required environment variables need to share a common prefix. To simplify configuration and avoid redundancy, I propose adding a `prefix` argument to the `Env` constructor. For example:

```python
from environs import Env

env = Env(prefix='MYAPP_')

# Instead of accessing `env('MYAPP_VAR')`, we could directly access:
value = env('VAR')  # Automatically resolves to 'MYAPP_VAR'
```

This feature could enhance the usability of `environs` for projects that follow naming conventions for environment variables.

If this idea aligns with the project's direction, I'd be happy to implement it and submit a pull request.

Discussion:

Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index a74a556..a88b69d 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,13 @@
 # Changelog
 
+## 14.1.0 (unreleased)
+
+Features:
+
+- Add `prefix` parameter to `Env` constructor ([#384](https://github.com/sloria/environs/issues/384)).
+  Thanks [arthurc0102](https://github.com/sloria/environs/issues/384)
+  for the suggestion.
+
 ## 14.0.0 (2025-06-07)
 
 Features:
diff --git a/README.md b/README.md
index 136df0d..2761d81 100644
--- a/README.md
+++ b/README.md
@@ -158,7 +158,26 @@ assert env.int("B") == 123
 
 ## Handling prefixes
 
+Pass `prefix` to the constructor if all your environment variables have the same prefix.
+
+```python
+from environs import Env
+
+# export MYAPP_HOST=lolcathost
+# export MYAPP_PORT=3000
+
+
+env = Env(prefix="MYAPP_")
+
+host = env("HOST", "localhost")  # => 'lolcathost'
+port = env.int("PORT", 5000)  # => 3000
+```
+
+Alternatively, you can use the `prefixed` context manager.
+
 ```python
+from environs import env
+
 # export MYAPP_HOST=lolcathost
 # export MYAPP_PORT=3000
 
diff --git a/src/environs/__init__.py b/src/environs/__init__.py
index 181f13f..aa24300 100644
--- a/src/environs/__init__.py
+++ b/src/environs/__init__.py
@@ -353,14 +353,20 @@ class Env:
     dj_email_url = _func2method(_dj_email_url_parser, "dj_email_url")
     dj_cache_url = _func2method(_dj_cache_url_parser, "dj_cache_url")
 
-    def __init__(self, *, eager: _BoolType = True, expand_vars: _BoolType = False):
+    def __init__(
+        self,
+        *,
+        eager: _BoolType = True,
+        expand_vars: _BoolType = False,
+        prefix: _StrType | None = None,
+    ):
         self.eager = eager
         self._sealed: bool = False
         self.expand_vars = expand_vars
         self._fields: dict[_StrType, ma.fields.Field] = {}
         self._values: dict[_StrType, typing.Any] = {}
         self._errors: ErrorMapping = collections.defaultdict(list)
-        self._prefix: _StrType | None = None
+        self._prefix: _StrType | None = prefix
         self.__custom_parsers__: dict[_StrType, ParserMethod] = {}
 
     def __repr__(self) -> _StrType:


Test patch:
diff --git a/tests/test_environs.py b/tests/test_environs.py
index 9da2308..b132fd9 100644
--- a/tests/test_environs.py
+++ b/tests/test_environs.py
@@ -94,7 +94,7 @@ class TestCasting:
         assert env.list("LIST", ["1"]) == ["1"]
 
     # https://github.com/sloria/environs/issues/270
-    def test_list_with_default_list_and_subcast(self, set_env, env: environs.Env):
+    def test_list_with_default_list_and_subcast(self, env: environs.Env):
         expected = [("a", "b"), ("b", "c")]
         assert (
             env.list("LIST", expected, subcast=lambda s: tuple(s.split(":")))
@@ -642,6 +642,12 @@ class TestPrefix:
     def default_environ(self, set_env):
         set_env({"APP_STR": "foo", "APP_INT": "42"})
 
+    def test_prefix_passed_to_constructor(self):
+        env = environs.Env(prefix="APP_")
+        assert env.str("STR") == "foo"
+        assert env.int("INT") == 42
+        assert env("NOT_FOUND", "mydefault") == "mydefault"
+
     def test_prefixed(self, env: environs.Env):
         with env.prefixed("APP_"):
             assert env.str("STR") == "foo"
@@ -676,6 +682,13 @@ class TestNestedPrefix:
     def default_environ(self, set_env):
         set_env({"APP_STR": "foo", "APP_NESTED_INT": "42"})
 
+    def test_prefixed_with_prefix_set(self):
+        env = environs.Env(prefix="APP_")
+        assert env.str("STR") == "foo"
+        with env.prefixed("NESTED_"):
+            assert env.int("INT") == 42
+            assert env("NOT_FOUND", "mydefault") == "mydefault"
+
     def test_nested_prefixed(self, env: environs.Env):
         with env.prefixed("APP_"):
             with env.prefixed("NESTED_"):