dotenvplus 0.0.2__tar.gz → 0.0.4__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,25 +1,25 @@
1
- Metadata-Version: 2.1
1
+ Metadata-Version: 2.2
2
2
  Name: dotenvplus
3
- Version: 0.0.2
4
- Summary: Python library that handles interactions from Discord POST requests.
3
+ Version: 0.0.4
4
+ Summary: Reads key-value pairs from a .env file and supports multiple values with dynamic interpolation.
5
5
  Author-email: AlexFlipnote <root@alexflipnote.dev>
6
6
  License: MIT
7
7
  Project-URL: Homepage, https://github.com/AlexFlipnote/dotenvplus
8
8
  Project-URL: Repository, https://github.com/AlexFlipnote/dotenvplus
9
9
  Keywords: dotenv,env,config,environment,variables,key-value,parser
10
- Requires-Python: >=3.6.0
10
+ Requires-Python: >=3.7.0
11
11
  Description-Content-Type: text/markdown
12
12
  License-File: LICENSE
13
13
  Provides-Extra: dev
14
14
  Requires-Dist: pyright; extra == "dev"
15
- Requires-Dist: flake8; extra == "dev"
15
+ Requires-Dist: ruff; extra == "dev"
16
16
  Requires-Dist: toml; extra == "dev"
17
17
  Provides-Extra: maintainer
18
18
  Requires-Dist: twine; extra == "maintainer"
19
19
  Requires-Dist: wheel; extra == "maintainer"
20
20
  Requires-Dist: build; extra == "maintainer"
21
21
 
22
- # dotenvplus
22
+ # DotEnvPlus
23
23
  Reads key-value pairs from a .env file and supports multiple values with dynamic interpolation.
24
24
 
25
25
  The values returned by the DotEnv object is treated like a dictionary, so you can use it like a normal dictionary.
@@ -28,7 +28,7 @@ Some of the usual dictionary methods are also supported like `.items()`, `.keys(
28
28
  Goal is to make it easy to use environment variables in your code, while also supporting multiple values.
29
29
 
30
30
  ## Installing
31
- > You need **Python >=3.6** to use this library.
31
+ > You need **Python >=3.7** to use this library.
32
32
 
33
33
  ```bash
34
34
  pip install dotenvplus
@@ -1,4 +1,4 @@
1
- # dotenvplus
1
+ # DotEnvPlus
2
2
  Reads key-value pairs from a .env file and supports multiple values with dynamic interpolation.
3
3
 
4
4
  The values returned by the DotEnv object is treated like a dictionary, so you can use it like a normal dictionary.
@@ -7,7 +7,7 @@ Some of the usual dictionary methods are also supported like `.items()`, `.keys(
7
7
  Goal is to make it easy to use environment variables in your code, while also supporting multiple values.
8
8
 
9
9
  ## Installing
10
- > You need **Python >=3.6** to use this library.
10
+ > You need **Python >=3.7** to use this library.
11
11
 
12
12
  ```bash
13
13
  pip install dotenvplus
@@ -1,8 +1,9 @@
1
+ import os
1
2
  import re
2
3
 
3
4
  from typing import Any, Iterator, Optional, Tuple, List, Dict
4
5
 
5
- __version__ = "0.0.2"
6
+ __version__ = "0.0.4"
6
7
 
7
8
 
8
9
  class ParsingError(Exception):
@@ -12,6 +13,7 @@ class ParsingError(Exception):
12
13
  class DotEnv:
13
14
  """
14
15
  DotEnv is a dotenv parser for Python with additional type support.
16
+
15
17
  It supports parsing of string, integer, float, and boolean values.
16
18
 
17
19
  Arguments
@@ -19,6 +21,9 @@ class DotEnv:
19
21
  path: `str` | `None`
20
22
  The path to the .env file.
21
23
  If none are provided, it defaults to `./.env`
24
+ update_system_env: `bool`
25
+ If True, it will load the values to the instance's environment variables.
26
+ Be warned that this will only support string values.
22
27
  handle_key_not_found: `bool`
23
28
  If True, it will make the object return `None` for any key that is not found.
24
29
  Essentially simulating `dict().get("Key", None)`
@@ -34,18 +39,24 @@ class DotEnv:
34
39
  self,
35
40
  path: Optional[str] = None,
36
41
  *,
42
+ update_system_env: bool = False,
37
43
  handle_key_not_found: bool = False,
38
44
  ):
45
+ # General values
46
+ self.__env: dict[str, Any] = {}
47
+ self.__frozen: bool = False
48
+
49
+ # Defined values
50
+ self.__quotes: Tuple[str, ...] = ('"', "'")
51
+ self.__bools: Tuple[str, ...] = ("true", "false")
52
+ self.__none: Tuple[str, ...] = ("null", "none", "nil", "undefined")
53
+
39
54
  # RegEx patterns
40
55
  self.__re_keyvar = re.compile(r"^\s*([a-zA-Z0-9_]*)\s*=\s*(.+)$")
41
56
  self.__re_isdigit = re.compile(r"^(?:-)?\d+$")
42
57
  self.__re_isfloat = re.compile(r"^(?:-)?\d+\.\d+$")
43
58
  self.__re_var_call = re.compile(r"\$\{([a-zA-Z0-9_]*)\}")
44
59
 
45
- # General values
46
- self.__env: Dict[str, Any] = {}
47
- self.__quotes: Tuple[str, ...] = ("\"", "'")
48
-
49
60
  # Config for the parser
50
61
  self.__path: str = path or ".env"
51
62
  self.__handle_key_not_found: bool = handle_key_not_found
@@ -53,10 +64,16 @@ class DotEnv:
53
64
  # Finally, the parser
54
65
  self.__parser()
55
66
 
67
+ if update_system_env:
68
+ os.environ.update({
69
+ key: str(value)
70
+ for key, value in self.__env.items()
71
+ })
72
+
56
73
  def __repr__(self) -> str:
57
74
  return f"<DotEnv data={self.__env}>"
58
75
 
59
- def __getitem__(self, key: str) -> Any:
76
+ def __getitem__(self, key: str) -> Any: # noqa: ANN401
60
77
  if self.__handle_key_not_found:
61
78
  return self.__env.get(key, None)
62
79
  return self.__env[key]
@@ -73,6 +90,19 @@ class DotEnv:
73
90
  def __iter__(self) -> Iterator[Tuple[str, Any]]:
74
91
  return iter(self.__env.items())
75
92
 
93
+ def __contains__(self, key: str) -> bool:
94
+ return key in self.__env
95
+
96
+ def __setitem__(self, key: str, value: Any) -> None: # noqa: ANN401
97
+ if self.__frozen:
98
+ raise AttributeError("This DotEnv object is read-only.")
99
+ self.__env[key] = value
100
+
101
+ def __delitem__(self, key: str) -> None:
102
+ if self.__frozen:
103
+ raise AttributeError("This DotEnv object is read-only.")
104
+ del self.__env[key]
105
+
76
106
  @property
77
107
  def keys(self) -> List[str]:
78
108
  """ `list[str]`: Returns a list of the keys. """
@@ -83,7 +113,7 @@ class DotEnv:
83
113
  """ `list[Any]`: Returns a list of the values. """
84
114
  return list(self.__env.values())
85
115
 
86
- def get(self, key: str, default: Any = None) -> Any:
116
+ def get(self, key: str, default: Any = None) -> Any: # noqa: ANN401
87
117
  """ `Any`: Return the value for key if key is in the dictionary, else default. """
88
118
  return self.__env.get(key, default)
89
119
 
@@ -102,6 +132,7 @@ class DotEnv:
102
132
  def __parser(self) -> None:
103
133
  """
104
134
  Parse the .env file and store the values in a dictionary.
135
+
105
136
  The keys are accessible later by using the square bracket notation
106
137
  directly on the DotEnv object.
107
138
 
@@ -112,22 +143,24 @@ class DotEnv:
112
143
  `ParsingError`
113
144
  If one of the values cannot be parsed.
114
145
  """
115
- with open(self.__path, "r", encoding="utf-8") as f:
116
- data: List[str] = f.readlines()
146
+ with open(self.__path, encoding="utf-8") as f:
147
+ data: list[str] = f.readlines()
117
148
 
118
- for line in data:
149
+ for line_no, line in enumerate(data, start=1):
119
150
  line = line.strip()
120
151
 
121
152
  if line.startswith("#") or line == "":
122
153
  # Ignore comment or empty line
123
154
  continue
124
155
 
125
- _find_kv = self.__re_keyvar.search(line)
126
- if not _find_kv:
127
- raise ParsingError(f"Expected key=value format, got '{line}'")
156
+ find_kv = self.__re_keyvar.search(line)
157
+ if not find_kv:
158
+ raise ParsingError(
159
+ f"Error at line {line_no}: "
160
+ f"Expected key=value format, got '{line}'"
161
+ )
128
162
 
129
- key, value = _find_kv.groups()
130
- _force_string = False
163
+ key, value = find_kv.groups()
131
164
 
132
165
  # Replace any variables in the value
133
166
  value = self.__re_var_call.sub(
@@ -135,29 +168,30 @@ class DotEnv:
135
168
  str(value)
136
169
  )
137
170
 
138
- # Remove quotes, but mark it as forced string from now
171
+ # Remove comment on the value itself too (if any)
172
+ value = value.split("#")[0].strip()
173
+
139
174
  if (
140
175
  value.startswith(self.__quotes) and
141
176
  value.endswith(self.__quotes)
142
177
  ):
178
+ # Remove quotes and skip the parsing step
143
179
  value = value[1:-1]
144
- _force_string = True
145
-
146
- if not _force_string:
147
180
 
181
+ else:
182
+ # String is not forced, go ahead and parse it
148
183
  if self.__re_isdigit.search(value):
149
184
  value = int(value)
150
185
 
151
186
  elif self.__re_isfloat.search(value):
152
187
  value = float(value)
153
188
 
154
- elif value.lower() in ("true", "false"):
189
+ elif value.lower() in self.__bools:
155
190
  value = value.lower() == "true"
156
191
 
157
- elif value.lower() in ("null", "none", "nil", "undefined"):
192
+ elif value.lower() in self.__none:
158
193
  value = None
159
194
 
160
- else:
161
- value = value
162
-
163
195
  self.__env[key] = value
196
+
197
+ self.__frozen = True
@@ -1,25 +1,25 @@
1
- Metadata-Version: 2.1
1
+ Metadata-Version: 2.2
2
2
  Name: dotenvplus
3
- Version: 0.0.2
4
- Summary: Python library that handles interactions from Discord POST requests.
3
+ Version: 0.0.4
4
+ Summary: Reads key-value pairs from a .env file and supports multiple values with dynamic interpolation.
5
5
  Author-email: AlexFlipnote <root@alexflipnote.dev>
6
6
  License: MIT
7
7
  Project-URL: Homepage, https://github.com/AlexFlipnote/dotenvplus
8
8
  Project-URL: Repository, https://github.com/AlexFlipnote/dotenvplus
9
9
  Keywords: dotenv,env,config,environment,variables,key-value,parser
10
- Requires-Python: >=3.6.0
10
+ Requires-Python: >=3.7.0
11
11
  Description-Content-Type: text/markdown
12
12
  License-File: LICENSE
13
13
  Provides-Extra: dev
14
14
  Requires-Dist: pyright; extra == "dev"
15
- Requires-Dist: flake8; extra == "dev"
15
+ Requires-Dist: ruff; extra == "dev"
16
16
  Requires-Dist: toml; extra == "dev"
17
17
  Provides-Extra: maintainer
18
18
  Requires-Dist: twine; extra == "maintainer"
19
19
  Requires-Dist: wheel; extra == "maintainer"
20
20
  Requires-Dist: build; extra == "maintainer"
21
21
 
22
- # dotenvplus
22
+ # DotEnvPlus
23
23
  Reads key-value pairs from a .env file and supports multiple values with dynamic interpolation.
24
24
 
25
25
  The values returned by the DotEnv object is treated like a dictionary, so you can use it like a normal dictionary.
@@ -28,7 +28,7 @@ Some of the usual dictionary methods are also supported like `.items()`, `.keys(
28
28
  Goal is to make it easy to use environment variables in your code, while also supporting multiple values.
29
29
 
30
30
  ## Installing
31
- > You need **Python >=3.6** to use this library.
31
+ > You need **Python >=3.7** to use this library.
32
32
 
33
33
  ```bash
34
34
  pip install dotenvplus
@@ -1,7 +1,7 @@
1
1
 
2
2
  [dev]
3
3
  pyright
4
- flake8
4
+ ruff
5
5
  toml
6
6
 
7
7
  [maintainer]
@@ -0,0 +1,167 @@
1
+ [build-system]
2
+ requires = ["setuptools", "wheel"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [tool.pdm.source]
6
+ name = "public"
7
+ url = "https://github.com/AlexFlipnote/dotenvplus"
8
+
9
+ [project]
10
+ name = "dotenvplus"
11
+ description = "Reads key-value pairs from a .env file and supports multiple values with dynamic interpolation."
12
+ requires-python = ">=3.7.0"
13
+ license = {text = "MIT"}
14
+
15
+ dynamic = ["version"]
16
+ readme = "README.md"
17
+ keywords = ["dotenv", "env", "config", "environment", "variables", "key-value", "parser"]
18
+ authors = [{name = "AlexFlipnote", email = "root@alexflipnote.dev"}]
19
+
20
+ dependencies = []
21
+
22
+ [project.urls]
23
+ Homepage = "https://github.com/AlexFlipnote/dotenvplus"
24
+ Repository = "https://github.com/AlexFlipnote/dotenvplus"
25
+
26
+ [project.optional-dependencies]
27
+ dev = ["pyright", "ruff", "toml"]
28
+ maintainer = ["twine", "wheel", "build"]
29
+
30
+ [tool.setuptools]
31
+ packages = [
32
+ "dotenvplus",
33
+ ]
34
+
35
+ [tool.setuptools.dynamic]
36
+ version = {attr = "dotenvplus.__version__"}
37
+ readme = {file = ["README.md"]}
38
+
39
+ [tool.ruff]
40
+ target-version = "py37"
41
+ indent-width = 4
42
+ line-length = 256 # Debating to change this.. not sure yet
43
+ output-format = "concise"
44
+
45
+ # Files to include/exclude
46
+ include = [
47
+ "dotenvplus/*",
48
+ ]
49
+ exclude = [
50
+ "**/__pycache__",
51
+ "**/*.pyc",
52
+ "*.egg-info",
53
+ ".venv",
54
+ "build",
55
+ "docs",
56
+ "dist",
57
+ "tests",
58
+ "examples",
59
+ ]
60
+
61
+ [tool.ruff.lint]
62
+ select = [
63
+ "A", # flake8-builtins
64
+ "ANN", # Annotations
65
+ "ARG", # flake8-unused-arguments
66
+ "B", # flake8-bugbear
67
+ "D", # pydocstyle
68
+ "DTZ", # flake8-datetimez
69
+ "E", # Error
70
+ "ERA", # eradicate
71
+ "F", # Pyflakes
72
+ "FIX", # flake8-fixme
73
+ "FLY", # flynt
74
+ "FURB", # refurb
75
+ "ICN", # flake8-import-conventions
76
+ "ISC", # flake8-implicit-str-concat
77
+ "N", # pep8-naming
78
+ "PIE", # flake8-pie
79
+ "PLE", # Pylint Errors
80
+ "PLW", # Pylint Warnings
81
+ "Q", # flake8-quotes
82
+ "RET", # flake8-return
83
+ "RUF", # Ruff-specific rules
84
+ "SIM", # flake8-simplify
85
+ "T", # flake8-print
86
+ "TC", # type-checking
87
+ "UP", # pyupgrade
88
+ "W", # Warning
89
+ ]
90
+
91
+ extend-select = ["W", "E"]
92
+ preview = true
93
+
94
+ ignore = [
95
+ # Docstring: function
96
+ "D100", # docstring in public module
97
+ "D104", # Missing docstring in public package
98
+ "D203", # 1 blank line required before class docstring
99
+ "D204", # 1 blank line required after class docstring
100
+ "D210", # No whitespaces surrounding docstring text
101
+ "D212", # Multi-line docstring start at first line
102
+ "D400", # Strictly must end with a period
103
+ "D401", # First line should be in imperative mood
104
+ "D413", # Missing blank line after last section
105
+ "D416", # Section name should end with a colon
106
+
107
+ # Annotations
108
+ "ANN003", # Missing type annotation for **kwargs
109
+ "ANN204", # Missing for __init__ method
110
+
111
+ # Type-checking
112
+ "TC001", "TC002", "TC003", "TC004", # Moving imports
113
+
114
+ # Docstring: Missing docs
115
+ "D101", # class
116
+ "D105", # magic method docstring required (__str__, __int__, etc)
117
+ "D107", # __init__ docstring required
118
+
119
+ # Except cases
120
+ "B904", # raise in except
121
+
122
+ # Simplify
123
+ "SIM105", # Use contextlib.suppress
124
+ "SIM114", # Merge if-else to one-liner
125
+
126
+ # Refurb
127
+ "FURB101", # read-whole-file
128
+ "FURB103", # write-whole-file
129
+
130
+ # Pylint Warnings
131
+ "PLW2901", # Overwrite loop control variable
132
+
133
+ # Type hints
134
+ "UP037", # type hints with quotes
135
+
136
+ # Variable shadowing
137
+ "A005", # Files
138
+ ]
139
+
140
+ [tool.ruff.format]
141
+ quote-style = "double"
142
+ indent-style = "space"
143
+ line-ending = "auto"
144
+
145
+ [tool.pyright]
146
+ reportOptionalOperand = "none"
147
+ reportOptionalSubscript = "none"
148
+ reportOptionalMemberAccess = "none"
149
+ reportUnnecessaryTypeIgnoreComment = "warning"
150
+ typeCheckingMode = "basic"
151
+ pythonVersion = "3.7"
152
+
153
+ include = [
154
+ "dotenvplus/*",
155
+ ]
156
+
157
+ exclude = [
158
+ "**/__pycache__",
159
+ "**/*.pyc",
160
+ "*.egg-info",
161
+ ".venv",
162
+ "build",
163
+ "docs",
164
+ "dist",
165
+ "tests",
166
+ "examples",
167
+ ]
@@ -11,9 +11,11 @@ class TestDotEnv(unittest.TestCase):
11
11
  "# Comment line\n"
12
12
  "STRING_KEY=HelloWorld\n"
13
13
  "INT_KEY=1234\n"
14
+ "STR_INT_KEY='1234'\n"
14
15
  "FLOAT_KEY=12.34\n"
15
16
  "BOOL_TRUE_KEY=true\n"
16
17
  "BOOL_FALSE_KEY=false\n"
18
+ "COMMENT_KEY=comment # Comment here\n"
17
19
  "NULL_KEY=null\n"
18
20
  "NONE_KEY=none\n"
19
21
  "NIL_KEY=nil\n"
@@ -35,6 +37,7 @@ class TestDotEnv(unittest.TestCase):
35
37
  self.assertIsInstance(dotenv, DotEnv)
36
38
  self.assertIsInstance(dotenv.get("STRING_KEY"), str)
37
39
  self.assertIsInstance(dotenv.get("INT_KEY"), int)
40
+ self.assertIsInstance(dotenv.get("STR_INT_KEY"), str)
38
41
  self.assertIsInstance(dotenv.get("FLOAT_KEY"), float)
39
42
  self.assertIsInstance(dotenv.get("BOOL_TRUE_KEY"), bool)
40
43
  self.assertIsInstance(dotenv.get("BOOL_FALSE_KEY"), bool)
@@ -42,6 +45,14 @@ class TestDotEnv(unittest.TestCase):
42
45
  self.assertIsInstance(dotenv.get("NONE_KEY"), type(None))
43
46
  self.assertIsInstance(dotenv.get("NIL_KEY"), type(None))
44
47
 
48
+ def test_comment_removed(self):
49
+ dotenv = DotEnv(self.file_path)
50
+ self.assertNotIn("#", dotenv.get("COMMENT_KEY"))
51
+
52
+ def test_key_in_env(self):
53
+ dotenv = DotEnv(self.file_path)
54
+ self.assertIn("STRING_KEY", dotenv)
55
+
45
56
  def test_raises_error_on_missing_file(self):
46
57
  with self.assertRaises(FileNotFoundError):
47
58
  DotEnv("missing_file.env")
@@ -1,62 +0,0 @@
1
- [build-system]
2
- requires = ["setuptools", "wheel"]
3
- build-backend = "setuptools.build_meta"
4
-
5
- [tool.pdm.source]
6
- name = "public"
7
- url = "https://github.com/AlexFlipnote/dotenvplus"
8
-
9
- [project]
10
- name = "dotenvplus"
11
- description = "Python library that handles interactions from Discord POST requests."
12
- requires-python = ">=3.6.0"
13
- license = {text = "MIT"}
14
-
15
- dynamic = ["version"]
16
- readme = "README.md"
17
- keywords = ["dotenv", "env", "config", "environment", "variables", "key-value", "parser"]
18
- authors = [{name = "AlexFlipnote", email = "root@alexflipnote.dev"}]
19
-
20
- dependencies = []
21
-
22
- [project.urls]
23
- Homepage = "https://github.com/AlexFlipnote/dotenvplus"
24
- Repository = "https://github.com/AlexFlipnote/dotenvplus"
25
-
26
- [project.optional-dependencies]
27
- dev = ["pyright", "flake8", "toml"]
28
- maintainer = ["twine", "wheel", "build"]
29
-
30
- [tool.setuptools]
31
- packages = [
32
- "dotenvplus",
33
- ]
34
-
35
- [tool.setuptools.dynamic]
36
- version = {attr = "dotenvplus.__version__"}
37
- readme = {file = ["README.md"]}
38
-
39
- [tool.flake8]
40
- max-line-length = 128
41
- ignore = [
42
- "D210", "D400", "D401", "D100", "D202", "D413", "D107",
43
- "D101", "D103", "D102", "E121", "D205", "D209", "D105",
44
- "E252", "W605", "W504", "E128", "E124", "E999", "W504"
45
- ]
46
-
47
- [tool.pyright]
48
- reportOptionalOperand = "none"
49
- reportOptionalSubscript = "none"
50
- reportOptionalMemberAccess = "none"
51
- reportUnnecessaryTypeIgnoreComment = "warning"
52
- typeCheckingMode = "basic"
53
- pythonVersion = "3.6"
54
-
55
- include = [
56
- "dotenvplus",
57
- ]
58
-
59
- exclude = [
60
- "**/__pycache__", "**/*.pyc", "*.egg-info",
61
- ".venv", "build", "docs", "dist",
62
- ]
File without changes
File without changes