envstack 0.6.2__tar.gz → 0.6.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.
Files changed (27) hide show
  1. {envstack-0.6.2/lib/envstack.egg-info → envstack-0.6.4}/PKG-INFO +25 -22
  2. {envstack-0.6.2 → envstack-0.6.4}/README.md +23 -20
  3. {envstack-0.6.2 → envstack-0.6.4}/lib/envstack/__init__.py +2 -2
  4. {envstack-0.6.2 → envstack-0.6.4}/lib/envstack/cli.py +9 -2
  5. {envstack-0.6.2 → envstack-0.6.4}/lib/envstack/config.py +4 -5
  6. {envstack-0.6.2 → envstack-0.6.4}/lib/envstack/env.py +173 -37
  7. envstack-0.6.4/lib/envstack/util.py +106 -0
  8. {envstack-0.6.2 → envstack-0.6.4/lib/envstack.egg-info}/PKG-INFO +25 -22
  9. {envstack-0.6.2 → envstack-0.6.4}/lib/envstack.egg-info/SOURCES.txt +1 -0
  10. envstack-0.6.4/lib/envstack.egg-info/requires.txt +1 -0
  11. {envstack-0.6.2 → envstack-0.6.4}/setup.py +2 -2
  12. envstack-0.6.2/lib/envstack.egg-info/requires.txt +0 -1
  13. {envstack-0.6.2 → envstack-0.6.4}/LICENSE +0 -0
  14. {envstack-0.6.2 → envstack-0.6.4}/dev.env +0 -0
  15. {envstack-0.6.2 → envstack-0.6.4}/dist.json +0 -0
  16. {envstack-0.6.2 → envstack-0.6.4}/lib/envstack/exceptions.py +0 -0
  17. {envstack-0.6.2 → envstack-0.6.4}/lib/envstack/logger.py +0 -0
  18. {envstack-0.6.2 → envstack-0.6.4}/lib/envstack/path.py +0 -0
  19. {envstack-0.6.2 → envstack-0.6.4}/lib/envstack/wrapper.py +0 -0
  20. {envstack-0.6.2 → envstack-0.6.4}/lib/envstack.egg-info/dependency_links.txt +0 -0
  21. {envstack-0.6.2 → envstack-0.6.4}/lib/envstack.egg-info/entry_points.txt +0 -0
  22. {envstack-0.6.2 → envstack-0.6.4}/lib/envstack.egg-info/not-zip-safe +0 -0
  23. {envstack-0.6.2 → envstack-0.6.4}/lib/envstack.egg-info/top_level.txt +0 -0
  24. {envstack-0.6.2 → envstack-0.6.4}/setup.cfg +0 -0
  25. {envstack-0.6.2 → envstack-0.6.4}/stack.env +0 -0
  26. {envstack-0.6.2 → envstack-0.6.4}/tests/test_env.py +0 -0
  27. {envstack-0.6.2 → envstack-0.6.4}/tests/test_path.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: envstack
3
- Version: 0.6.2
3
+ Version: 0.6.4
4
4
  Summary: Stacked environment variable management system
5
5
  Home-page: http://github.com/rsgalloway/envstack
6
6
  Author: Ryan Galloway
@@ -19,7 +19,7 @@ Classifier: Programming Language :: Python :: 3.11
19
19
  Requires-Python: >=3.6
20
20
  Description-Content-Type: text/markdown
21
21
  License-File: LICENSE
22
- Requires-Dist: PyYAML>=5.1.2
22
+ Requires-Dist: PyYAML==5.1.2
23
23
 
24
24
  envstack
25
25
  ========
@@ -184,19 +184,34 @@ $ envstack [STACK] --sources
184
184
 
185
185
  ## Python API
186
186
 
187
- To init the environment stack, use the `init` function:
187
+ To initialize the environment stack in Python, use the `init` function:
188
188
 
189
189
  ```python
190
- >>> envstack.init("thing")
191
- >>> os.getenv("FOO")
192
- 'bar'
190
+ >>> envstack.init()
191
+ >>> os.getenv("HELLO")
192
+ 'world'
193
+ ```
194
+
195
+ To initialize the "dev" stack:
196
+
197
+ ```python
198
+ >>> envstack.init("dev")
199
+ >>> os.getenv("ENV")
200
+ 'dev'
201
+ ```
202
+
203
+ To revert the original environment:
204
+
205
+ ```python
206
+ >>> envstack.revert()
207
+ >>> os.getenv("HELLO")
208
+ >>>
193
209
  ```
194
210
 
195
211
  Alternatively, `envstack.getenv` can be a drop-in replacement for `os.getenv`
196
212
  for the default environment stack:
197
213
 
198
214
  ```python
199
- >>> import envstack
200
215
  >>> envstack.getenv("HELLO")
201
216
  'world'
202
217
  ```
@@ -288,7 +303,7 @@ the output of the `--export` command:
288
303
 
289
304
  #### bash
290
305
  ```bash
291
- alias envstack-set='source <(envstack "$1" --export)';
306
+ alias envstack-init='source <(envstack --export)';
292
307
  ```
293
308
 
294
309
  #### cmd
@@ -296,13 +311,13 @@ alias envstack-set='source <(envstack "$1" --export)';
296
311
  doskey envstack-set=for /f "usebackq" %i in (`envstack --export $*`) do %%i
297
312
  ```
298
313
 
299
- Then you can set the environment stack in your shell with the `envstack-set`
314
+ Then you can set the environment stack in your shell with the `envstack-init`
300
315
  command. To clear the environment in your current shell, create an alias that
301
316
  sources the output of the `--clear` command:
302
317
 
303
318
  #### bash
304
319
  ```bash
305
- alias envstack-clear='source <(envstack "$1" --clear)';
320
+ alias envstack-clear='source <(envstack --clear)';
306
321
  ```
307
322
 
308
323
  #### cmd
@@ -310,18 +325,6 @@ alias envstack-clear='source <(envstack "$1" --clear)';
310
325
  doskey envstack-clear=for /f "usebackq" %i in (`envstack --clear $*`) do %%i
311
326
  ```
312
327
 
313
- Create a function for convenience that does both in one command:
314
-
315
- #### bash
316
- ```bash
317
- envstack-init() { envstack-clear "$1"; envstack-set "$1"; }
318
- ```
319
-
320
- #### cmd
321
- ```cmd
322
- doskey envstack-init=envstack-clear $* & envstack-set $*
323
- ```
324
-
325
328
  ## Config
326
329
 
327
330
  Default config settings are in the config.py module. The following environment
@@ -161,19 +161,34 @@ $ envstack [STACK] --sources
161
161
 
162
162
  ## Python API
163
163
 
164
- To init the environment stack, use the `init` function:
164
+ To initialize the environment stack in Python, use the `init` function:
165
165
 
166
166
  ```python
167
- >>> envstack.init("thing")
168
- >>> os.getenv("FOO")
169
- 'bar'
167
+ >>> envstack.init()
168
+ >>> os.getenv("HELLO")
169
+ 'world'
170
+ ```
171
+
172
+ To initialize the "dev" stack:
173
+
174
+ ```python
175
+ >>> envstack.init("dev")
176
+ >>> os.getenv("ENV")
177
+ 'dev'
178
+ ```
179
+
180
+ To revert the original environment:
181
+
182
+ ```python
183
+ >>> envstack.revert()
184
+ >>> os.getenv("HELLO")
185
+ >>>
170
186
  ```
171
187
 
172
188
  Alternatively, `envstack.getenv` can be a drop-in replacement for `os.getenv`
173
189
  for the default environment stack:
174
190
 
175
191
  ```python
176
- >>> import envstack
177
192
  >>> envstack.getenv("HELLO")
178
193
  'world'
179
194
  ```
@@ -265,7 +280,7 @@ the output of the `--export` command:
265
280
 
266
281
  #### bash
267
282
  ```bash
268
- alias envstack-set='source <(envstack "$1" --export)';
283
+ alias envstack-init='source <(envstack --export)';
269
284
  ```
270
285
 
271
286
  #### cmd
@@ -273,13 +288,13 @@ alias envstack-set='source <(envstack "$1" --export)';
273
288
  doskey envstack-set=for /f "usebackq" %i in (`envstack --export $*`) do %%i
274
289
  ```
275
290
 
276
- Then you can set the environment stack in your shell with the `envstack-set`
291
+ Then you can set the environment stack in your shell with the `envstack-init`
277
292
  command. To clear the environment in your current shell, create an alias that
278
293
  sources the output of the `--clear` command:
279
294
 
280
295
  #### bash
281
296
  ```bash
282
- alias envstack-clear='source <(envstack "$1" --clear)';
297
+ alias envstack-clear='source <(envstack --clear)';
283
298
  ```
284
299
 
285
300
  #### cmd
@@ -287,18 +302,6 @@ alias envstack-clear='source <(envstack "$1" --clear)';
287
302
  doskey envstack-clear=for /f "usebackq" %i in (`envstack --clear $*`) do %%i
288
303
  ```
289
304
 
290
- Create a function for convenience that does both in one command:
291
-
292
- #### bash
293
- ```bash
294
- envstack-init() { envstack-clear "$1"; envstack-set "$1"; }
295
- ```
296
-
297
- #### cmd
298
- ```cmd
299
- doskey envstack-init=envstack-clear $* & envstack-set $*
300
- ```
301
-
302
305
  ## Config
303
306
 
304
307
  Default config settings are in the config.py module. The following environment
@@ -34,6 +34,6 @@ Stacked environment variable management system.
34
34
  """
35
35
 
36
36
  __prog__ = "envstack"
37
- __version__ = "0.6.2"
37
+ __version__ = "0.6.4"
38
38
 
39
- from envstack.env import Env, getenv, init, load_file
39
+ from envstack.env import Env, clear, getenv, init, load_file, revert, save
@@ -38,7 +38,14 @@ import sys
38
38
  import traceback
39
39
 
40
40
  from envstack import __version__, config
41
- from envstack.env import build_sources, expandvars, export, load_environ, trace_var
41
+ from envstack.env import (
42
+ build_sources,
43
+ expandvars,
44
+ clear,
45
+ export,
46
+ load_environ,
47
+ trace_var,
48
+ )
42
49
  from envstack.wrapper import run_command
43
50
 
44
51
 
@@ -139,7 +146,7 @@ def main():
139
146
  for source in sources:
140
147
  print(source)
141
148
  elif args.clear:
142
- print(export(args.namespace, config.SHELL, clear=True))
149
+ print(clear(args.namespace, config.SHELL))
143
150
  elif args.export:
144
151
  print(export(args.namespace, config.SHELL))
145
152
  elif command:
@@ -76,14 +76,13 @@ DEFAULT_ENV = {
76
76
  "USER": USERNAME,
77
77
  }
78
78
 
79
- # default location of envstack files
80
- # NOTE: the siteconf sitecustomize.py module may override this
79
+ # default location of env stack .env files
81
80
  DEFAULT_ENV_DIR = os.getenv(
82
81
  "DEFAULT_ENV_DIR",
83
82
  {
84
- "darwin": "{HOME}/Library/Application Support/envstack",
85
- "linux": "{HOME}/.local/envstack",
86
- "windows": "C:\\ProgramData\\envstack",
83
+ "darwin": "{HOME}/Library/Application Support/pipe/{ENV}/env",
84
+ "linux": "{HOME}/.local/pipe/{ENV}/env",
85
+ "windows": "C:\\ProgramData\\pipe\\{ENV}\\env",
87
86
  }
88
87
  .get(PLATFORM)
89
88
  .format(**DEFAULT_ENV),
@@ -36,8 +36,9 @@ Contains functions and classes for processing scoped .env files.
36
36
  import os
37
37
  import re
38
38
  import string
39
+ import sys
39
40
 
40
- from envstack import config, logger, path
41
+ from envstack import config, logger, path, util
41
42
  from envstack.exceptions import *
42
43
 
43
44
  # value delimiter pattern (splits values by colons or semicolons)
@@ -49,6 +50,9 @@ load_file_cache = {}
49
50
  # value for unresolvable variables
50
51
  null = ""
51
52
 
53
+ # stores the original environment
54
+ SAVED_ENVIRONMENT = None
55
+
52
56
 
53
57
  class EnvVar(string.Template, str):
54
58
  """A string class for supporting $-substitutions, e.g.: ::
@@ -529,65 +533,192 @@ def expandvars(var, env=None, recursive=False):
529
533
  return EnvVar(var).expand(env, recursive=recursive)
530
534
 
531
535
 
532
- def export(name, shell="bash", resolve=False, scope=None, clear=False):
533
- """Returns environment commands that can be sourced.
536
+ def clear(
537
+ name=config.DEFAULT_NAMESPACE,
538
+ shell=config.SHELL,
539
+ scope=None,
540
+ ):
541
+ """Returns shell commands that can be sourced to unset or restore env stack
542
+ environment variables. Should only be run after a previous export:
534
543
 
535
- $ source <(envstack --export)
544
+ $ source <(envstack --export)
545
+ $ source <(envstack --clear)
536
546
 
537
- or to clear existing values:
547
+ List of shell names: bash, sh, tcsh, cmd, pwsh
548
+ (see output of config.detect_shell()).
538
549
 
539
- $ source <(envstack --clear)
550
+ :param name: stack namespace.
551
+ :param shell: name of shell (default: current shell).
552
+ :param scope: environment scope (default: cwd).
553
+ :returns: shell commands as string.
554
+ """
555
+ if "ENVSTACK" not in os.environ:
556
+ logger.log.info("Environment is already clear")
557
+ return ""
558
+
559
+ env = load_environ(name, environ=None, scope=scope)
560
+ export_vars = dict(env.items())
561
+ export_list = list()
562
+
563
+ # vars that should never be unset
564
+ restricted_vars = ["PATH", "PS1", "PWD", "PROMPT", "DEFAULT_ENV_DIR"]
565
+
566
+ for key in export_vars:
567
+ if key not in os.environ:
568
+ continue
569
+ old_key = f"_ES_OLD_{key}"
570
+ old_val = os.environ.get(old_key)
571
+ if shell in ["bash", "sh", "zsh"]:
572
+ if old_val:
573
+ export_list.append("export %s=%s" % (key, old_val))
574
+ export_list.append("unset %s" % (old_key))
575
+ elif key not in restricted_vars:
576
+ export_list.append(f"unset {key}")
577
+ elif shell == "tcsh":
578
+ if old_val:
579
+ export_list.append(f"setenv {key} {old_val}")
580
+ export_list.append(f"unsetenv {old_key}")
581
+ elif key not in restricted_vars:
582
+ export_list.append(f"unsetenv {key}")
583
+ elif shell == "cmd":
584
+ if old_val:
585
+ export_list.append(f"set {key}={old_val}")
586
+ export_list.append(f"set {old_key}=")
587
+ elif key not in restricted_vars:
588
+ export_list.append(f"set {key}=")
589
+ elif shell == "pwsh":
590
+ if old_val:
591
+ export_list.append(f"$env:{key}='{old_val}'")
592
+ export_list.append(f"Remove-Item Env:{old_key}")
593
+ elif key not in restricted_vars:
594
+ export_list.append(f"Remove-Item Env:{key}")
595
+ elif shell == "unknown":
596
+ raise Exception("unknown shell")
597
+
598
+ export_list.sort()
599
+ exp = "\n".join(export_list)
600
+
601
+ return exp
602
+
603
+
604
+ def export(
605
+ name=config.DEFAULT_NAMESPACE,
606
+ shell=config.SHELL,
607
+ scope=None,
608
+ ):
609
+ """Returns shell set env commands that can be sourced to set env stack
610
+ environment variables.
540
611
 
541
612
  List of shell names: bash, sh, tcsh, cmd, pwsh
542
613
  (see output of config.detect_shell()).
543
614
 
544
615
  :param name: stack namespace.
545
- :param shell: name of shell (default: bash).
546
- :param resolve: resolve values (default: True).
616
+ :param shell: name of shell (default: current shell).
547
617
  :param scope: environment scope (default: cwd).
548
- :param clear: clear existing values (default: False).
549
618
  :returns: shell commands as string.
550
619
  """
551
620
  env = load_environ(name, scope=scope)
552
- expList = list()
553
- for k, v in env.items():
554
- if resolve:
555
- v = expandvars(v, env, recursive=False)
621
+ export_vars = dict(env.items())
622
+ export_list = list()
623
+
624
+ for key, val in export_vars.items():
625
+ val = expandvars(val, env, recursive=False)
626
+ old_key = f"_ES_OLD_{key}"
627
+ old_val = os.environ.get(key)
628
+ if key == "PATH" and not val:
629
+ logger.log.warning("PATH is empty")
630
+ continue
556
631
  if shell in ["bash", "sh", "zsh"]:
557
- if clear:
558
- expList.append(f"unset {k}")
559
- else:
560
- expList.append(f'export {k}="{v}"')
632
+ export_list.append(f"export {key}={val}")
633
+ if old_val:
634
+ export_list.append(f"export {old_key}={old_val}")
561
635
  elif shell == "tcsh":
562
- if clear:
563
- expList.append(f"unsetenv {k}")
564
- else:
565
- expList.append(f'setenv {k}:"{v}"')
636
+ export_list.append(f'setenv {key}:"{val}"')
637
+ if old_val:
638
+ export_list.append(f'setenv {old_key}:"{old_val}"')
566
639
  elif shell == "cmd":
567
- if clear:
568
- expList.append(f"set {k}=")
569
- else:
570
- expList.append(f'set {k}="{v}"')
640
+ export_list.append(f'set {key}="{val}"')
641
+ if old_val:
642
+ export_list.append(f'set {old_key}="{old_val}"')
571
643
  elif shell == "pwsh":
572
- if clear:
573
- expList.append(f"Remove-Item Env:{k}")
574
- else:
575
- expList.append(f'$env:{k}="{v}"')
644
+ export_list.append(f'$env:{key}="{val}"')
645
+ if old_val:
646
+ export_list.append(f'$env:{old_key}="{old_val}"')
576
647
  elif shell == "unknown":
577
648
  raise Exception("unknown shell")
578
- expList.sort()
579
- exp = "\n".join(expList)
649
+
650
+ export_list.sort()
651
+ exp = "\n".join(export_list)
652
+
580
653
  return exp
581
654
 
582
655
 
656
+ def save():
657
+ """Saves the current environment for later restoration."""
658
+ global SAVED_ENVIRONMENT
659
+ if not SAVED_ENVIRONMENT:
660
+ SAVED_ENVIRONMENT = dict(os.environ.copy())
661
+ return SAVED_ENVIRONMENT
662
+
663
+
664
+ def revert():
665
+ """Reverts the environment to the saved environment. Updates sys.path using
666
+ paths found in PYTHONPATH.
667
+
668
+ Initialize the default environment stack:
669
+ >>> envstack.init()
670
+
671
+ Revert to the previous environment:
672
+ >>> envstack.revert()
673
+ """
674
+ global SAVED_ENVIRONMENT
675
+ if SAVED_ENVIRONMENT is None:
676
+ return
677
+
678
+ # clear current sys.path values
679
+ util.clear_sys_path()
680
+
681
+ # restore the original environment
682
+ os.environ.clear()
683
+ os.environ.update(SAVED_ENVIRONMENT)
684
+
685
+ # restore sys.path from PYTHONPATH
686
+ util.load_sys_path()
687
+
688
+ SAVED_ENVIRONMENT = None
689
+
690
+
583
691
  def init(name=config.DEFAULT_NAMESPACE):
584
- """Initializes the environment for a given namespace.
692
+ """Initializes the environment from a given stack namespace. Environments
693
+ propogate downwards with subsequent calls to init().
585
694
 
586
- :param name: namespace (basename of env files).
695
+ Updates sys.path using paths found in PYTHONPATH.
696
+
697
+ Initialize the default environment stack:
698
+ >>> envstack.init()
699
+
700
+ Initialize the dev environment stack (inherits from previous call):
701
+ >>> envstack.init('dev')
702
+
703
+ Revert to the original environment:
704
+ >>> envstack.revert()
705
+
706
+ :param name: stack namespace (default: 'stack').
707
+ :param reset: clear existing values first (default: False).
587
708
  """
709
+ # save environment to restore later using envstack.revert()
710
+ save()
711
+
712
+ # clear old sys.path values
713
+ util.clear_sys_path()
714
+
715
+ # load the stack and update the environment
588
716
  env = load_environ(name)
589
717
  os.environ.update(encode(env))
590
718
 
719
+ # update sys.path from PYTHONPATH
720
+ util.load_sys_path()
721
+
591
722
 
592
723
  def load_environ(
593
724
  name=config.DEFAULT_NAMESPACE,
@@ -616,7 +747,6 @@ def load_environ(
616
747
  :param includes: merge included namespaces.
617
748
  :returns: dict of environment variables.
618
749
  """
619
-
620
750
  # build list of sources from namespace(s) and scope
621
751
  if not sources:
622
752
  sources = build_sources(name, scope=scope, includes=includes)
@@ -631,6 +761,9 @@ def load_environ(
631
761
  for source in sources:
632
762
  env.update(source.load(platform=platform))
633
763
 
764
+ # store the name of the environment stack
765
+ env["ENVSTACK"] = env.get("ENVSTACK", "|".join(name))
766
+
634
767
  # merge values from given environment
635
768
  if environ:
636
769
  return merge(env, environ, platform=platform)
@@ -688,9 +821,11 @@ def merge(env, other, strict=False, platform=config.PLATFORM):
688
821
  """
689
822
  merged = env.copy()
690
823
  for key, value in merged.items():
691
- var = "${%s}" % key
824
+ varstr = "${%s}" % key
825
+ # replace variables in other with values from env
692
826
  if key in other:
693
- if var in str(value):
827
+ # replace variables in value with values from other
828
+ if varstr in str(value):
694
829
  value = re.sub(
695
830
  r"\${(\w+)}",
696
831
  lambda match: other.get(match.group(1), match.group(0)),
@@ -699,7 +834,8 @@ def merge(env, other, strict=False, platform=config.PLATFORM):
699
834
  elif not strict:
700
835
  value = other.get(key)
701
836
  else:
702
- value = str(value).replace(var, "")
837
+ value = str(value).replace(varstr, "")
838
+ # replace colons with semicolons on windows
703
839
  if platform == "windows":
704
840
  result = re.sub(r"(?<!\b[A-Za-z]):", ";", str(value))
705
841
  value = result.rstrip(";")
@@ -0,0 +1,106 @@
1
+ #!/usr/bin/env python
2
+ #
3
+ # Copyright (c) 2024, Ryan Galloway (ryan@rsgalloway.com)
4
+ #
5
+ # Redistribution and use in source and binary forms, with or without
6
+ # modification, are permitted provided that the following conditions are met:
7
+ #
8
+ # - Redistributions of source code must retain the above copyright notice,
9
+ # this list of conditions and the following disclaimer.
10
+ #
11
+ # - Redistributions in binary form must reproduce the above copyright notice,
12
+ # this list of conditions and the following disclaimer in the documentation
13
+ # and/or other materials provided with the distribution.
14
+ #
15
+ # - Neither the name of the software nor the names of its contributors
16
+ # may be used to endorse or promote products derived from this software
17
+ # without specific prior written permission.
18
+ #
19
+ # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
20
+ # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
21
+ # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
22
+ # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
23
+ # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
24
+ # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
25
+ # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
26
+ # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
27
+ # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
28
+ # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
29
+ # POSSIBILITY OF SUCH DAMAGE.
30
+ #
31
+
32
+ __doc__ = """
33
+ Contains common utility functions and classes.
34
+ """
35
+
36
+ import os
37
+ import sys
38
+
39
+
40
+ def clear_sys_path(var="PYTHONPATH"):
41
+ """
42
+ Remove paths from sys.path that are in the given environment variable.
43
+
44
+ :param var: The environment variable to remove paths from.
45
+ """
46
+ for path in get_paths_from_var(var):
47
+ if path and path in sys.path:
48
+ sys.path.remove(path)
49
+
50
+
51
+ def load_sys_path(var="PYTHONPATH", pathsep=os.pathsep, reverse=True):
52
+ """
53
+ Add paths from the given environment variable to sys.path.
54
+
55
+ :param var: The environment variable to add paths from.
56
+ :param pathsep: The path separator to use.
57
+ :param reverse: Reverse the order of the paths.
58
+ """
59
+ for path in get_paths_from_var(var, pathsep, reverse):
60
+ if path and path not in sys.path:
61
+ sys.path.insert(0, path)
62
+
63
+
64
+ def dict_diff(dict1, dict2):
65
+ """
66
+ Compare two dictionaries and return their differences.
67
+
68
+ :param dict1: First dictionary.
69
+ :param dict2: Second dictionary.
70
+ :returns: diff dict: 'added', 'removed', 'changed', and 'unchanged'.
71
+ """
72
+ added = {k: dict2[k] for k in dict2 if k not in dict1}
73
+ removed = {k: dict1[k] for k in dict1 if k not in dict2}
74
+ changed = {
75
+ k: (dict1[k], dict2[k]) for k in dict1 if k in dict2 and dict1[k] != dict2[k]
76
+ }
77
+ unchanged = {k: dict1[k] for k in dict1 if k in dict2 and dict1[k] == dict2[k]}
78
+
79
+ return {
80
+ "added": added,
81
+ "removed": removed,
82
+ "changed": changed,
83
+ "unchanged": unchanged,
84
+ }
85
+
86
+
87
+ def get_paths_from_var(var="PYTHONPATH", pathsep=os.pathsep, reverse=True):
88
+ """Returns a list of paths from a given pathsep separated environment
89
+ variable.
90
+
91
+ :param var: The environment variable to get paths from.
92
+ :param pathsep: The path separator to use.
93
+ :param reverse: Reverse the order of the paths.
94
+ :returns: A list of paths.
95
+ """
96
+
97
+ paths = []
98
+ value = os.environ.get(var)
99
+
100
+ if value:
101
+ paths = value.split(pathsep)
102
+
103
+ if reverse:
104
+ paths.reverse()
105
+
106
+ return paths
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: envstack
3
- Version: 0.6.2
3
+ Version: 0.6.4
4
4
  Summary: Stacked environment variable management system
5
5
  Home-page: http://github.com/rsgalloway/envstack
6
6
  Author: Ryan Galloway
@@ -19,7 +19,7 @@ Classifier: Programming Language :: Python :: 3.11
19
19
  Requires-Python: >=3.6
20
20
  Description-Content-Type: text/markdown
21
21
  License-File: LICENSE
22
- Requires-Dist: PyYAML>=5.1.2
22
+ Requires-Dist: PyYAML==5.1.2
23
23
 
24
24
  envstack
25
25
  ========
@@ -184,19 +184,34 @@ $ envstack [STACK] --sources
184
184
 
185
185
  ## Python API
186
186
 
187
- To init the environment stack, use the `init` function:
187
+ To initialize the environment stack in Python, use the `init` function:
188
188
 
189
189
  ```python
190
- >>> envstack.init("thing")
191
- >>> os.getenv("FOO")
192
- 'bar'
190
+ >>> envstack.init()
191
+ >>> os.getenv("HELLO")
192
+ 'world'
193
+ ```
194
+
195
+ To initialize the "dev" stack:
196
+
197
+ ```python
198
+ >>> envstack.init("dev")
199
+ >>> os.getenv("ENV")
200
+ 'dev'
201
+ ```
202
+
203
+ To revert the original environment:
204
+
205
+ ```python
206
+ >>> envstack.revert()
207
+ >>> os.getenv("HELLO")
208
+ >>>
193
209
  ```
194
210
 
195
211
  Alternatively, `envstack.getenv` can be a drop-in replacement for `os.getenv`
196
212
  for the default environment stack:
197
213
 
198
214
  ```python
199
- >>> import envstack
200
215
  >>> envstack.getenv("HELLO")
201
216
  'world'
202
217
  ```
@@ -288,7 +303,7 @@ the output of the `--export` command:
288
303
 
289
304
  #### bash
290
305
  ```bash
291
- alias envstack-set='source <(envstack "$1" --export)';
306
+ alias envstack-init='source <(envstack --export)';
292
307
  ```
293
308
 
294
309
  #### cmd
@@ -296,13 +311,13 @@ alias envstack-set='source <(envstack "$1" --export)';
296
311
  doskey envstack-set=for /f "usebackq" %i in (`envstack --export $*`) do %%i
297
312
  ```
298
313
 
299
- Then you can set the environment stack in your shell with the `envstack-set`
314
+ Then you can set the environment stack in your shell with the `envstack-init`
300
315
  command. To clear the environment in your current shell, create an alias that
301
316
  sources the output of the `--clear` command:
302
317
 
303
318
  #### bash
304
319
  ```bash
305
- alias envstack-clear='source <(envstack "$1" --clear)';
320
+ alias envstack-clear='source <(envstack --clear)';
306
321
  ```
307
322
 
308
323
  #### cmd
@@ -310,18 +325,6 @@ alias envstack-clear='source <(envstack "$1" --clear)';
310
325
  doskey envstack-clear=for /f "usebackq" %i in (`envstack --clear $*`) do %%i
311
326
  ```
312
327
 
313
- Create a function for convenience that does both in one command:
314
-
315
- #### bash
316
- ```bash
317
- envstack-init() { envstack-clear "$1"; envstack-set "$1"; }
318
- ```
319
-
320
- #### cmd
321
- ```cmd
322
- doskey envstack-init=envstack-clear $* & envstack-set $*
323
- ```
324
-
325
328
  ## Config
326
329
 
327
330
  Default config settings are in the config.py module. The following environment
@@ -11,6 +11,7 @@ lib/envstack/env.py
11
11
  lib/envstack/exceptions.py
12
12
  lib/envstack/logger.py
13
13
  lib/envstack/path.py
14
+ lib/envstack/util.py
14
15
  lib/envstack/wrapper.py
15
16
  lib/envstack.egg-info/PKG-INFO
16
17
  lib/envstack.egg-info/SOURCES.txt
@@ -0,0 +1 @@
1
+ PyYAML==5.1.2
@@ -74,7 +74,7 @@ class PostInstallCommand(install):
74
74
 
75
75
  setup(
76
76
  name="envstack",
77
- version="0.6.2",
77
+ version="0.6.4",
78
78
  description="Stacked environment variable management system",
79
79
  long_description=long_description,
80
80
  long_description_content_type="text/markdown",
@@ -106,7 +106,7 @@ setup(
106
106
  ],
107
107
  },
108
108
  install_requires=[
109
- "PyYAML>=5.1.2",
109
+ "PyYAML==5.1.2",
110
110
  ],
111
111
  python_requires=">=3.6",
112
112
  data_files=[(".", ["stack.env", "dev.env", "dist.json"])],
@@ -1 +0,0 @@
1
- PyYAML>=5.1.2
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes