envstack 0.9.6__tar.gz → 1.0.1__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 (34) hide show
  1. {envstack-0.9.6 → envstack-1.0.1}/LICENSE +1 -1
  2. {envstack-0.9.6/lib/envstack.egg-info → envstack-1.0.1}/PKG-INFO +36 -10
  3. {envstack-0.9.6 → envstack-1.0.1}/README.md +35 -9
  4. {envstack-0.9.6 → envstack-1.0.1}/lib/envstack/__init__.py +3 -3
  5. {envstack-0.9.6 → envstack-1.0.1}/lib/envstack/cli.py +1 -1
  6. {envstack-0.9.6 → envstack-1.0.1}/lib/envstack/config.py +22 -2
  7. {envstack-0.9.6 → envstack-1.0.1}/lib/envstack/encrypt.py +1 -1
  8. {envstack-0.9.6 → envstack-1.0.1}/lib/envstack/env.py +1 -1
  9. {envstack-0.9.6 → envstack-1.0.1}/lib/envstack/envshell.py +2 -2
  10. {envstack-0.9.6 → envstack-1.0.1}/lib/envstack/exceptions.py +1 -1
  11. {envstack-0.9.6 → envstack-1.0.1}/lib/envstack/logger.py +1 -1
  12. {envstack-0.9.6 → envstack-1.0.1}/lib/envstack/node.py +25 -20
  13. envstack-1.0.1/lib/envstack/path.py +448 -0
  14. {envstack-0.9.6 → envstack-1.0.1}/lib/envstack/util.py +49 -7
  15. {envstack-0.9.6 → envstack-1.0.1}/lib/envstack/wrapper.py +63 -1
  16. {envstack-0.9.6 → envstack-1.0.1/lib/envstack.egg-info}/PKG-INFO +36 -10
  17. {envstack-0.9.6 → envstack-1.0.1}/lib/envstack.egg-info/SOURCES.txt +1 -0
  18. {envstack-0.9.6 → envstack-1.0.1}/pyproject.toml +1 -1
  19. {envstack-0.9.6 → envstack-1.0.1}/tests/test_cmds.py +27 -4
  20. {envstack-0.9.6 → envstack-1.0.1}/tests/test_encrypt.py +1 -1
  21. {envstack-0.9.6 → envstack-1.0.1}/tests/test_env.py +8 -3
  22. {envstack-0.9.6 → envstack-1.0.1}/tests/test_node.py +54 -1
  23. envstack-1.0.1/tests/test_path.py +189 -0
  24. {envstack-0.9.6 → envstack-1.0.1}/tests/test_util.py +82 -3
  25. envstack-1.0.1/tests/test_wrapper.py +313 -0
  26. envstack-0.9.6/lib/envstack/path.py +0 -375
  27. envstack-0.9.6/tests/test_wrapper.py +0 -161
  28. {envstack-0.9.6 → envstack-1.0.1}/dist.json +0 -0
  29. {envstack-0.9.6 → envstack-1.0.1}/lib/envstack.egg-info/dependency_links.txt +0 -0
  30. {envstack-0.9.6 → envstack-1.0.1}/lib/envstack.egg-info/entry_points.txt +0 -0
  31. {envstack-0.9.6 → envstack-1.0.1}/lib/envstack.egg-info/not-zip-safe +0 -0
  32. {envstack-0.9.6 → envstack-1.0.1}/lib/envstack.egg-info/requires.txt +0 -0
  33. {envstack-0.9.6 → envstack-1.0.1}/lib/envstack.egg-info/top_level.txt +0 -0
  34. {envstack-0.9.6 → envstack-1.0.1}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  envstack
2
2
 
3
- Copyright (c) 2024-2025, Ryan Galloway (ryan@rsgalloway.com)
3
+ Copyright (c) 2024-2026, Ryan Galloway (ryan@rsgalloway.com)
4
4
  All rights reserved.
5
5
 
6
6
  Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: envstack
3
- Version: 0.9.6
3
+ Version: 1.0.1
4
4
  Summary: Environment variable composition layer for tools and processes.
5
5
  Author-email: Ryan Galloway <ryan@rsgalloway.com>
6
6
  License: BSD 3-Clause License
@@ -41,9 +41,22 @@ Environment variable composition and activation layer for tools and processes.
41
41
  - Shared, policy-driven environments
42
42
  - Inspectable and deterministic behavior
43
43
 
44
- envstack focuses on **configuration and activation**, not dependency resolution.
44
+ envstack environments are layered hierarchically, with later layers inheriting
45
+ from and overriding earlier ones.
45
46
 
46
- For the core concepts, see `docs/index.md`.
47
+ ```mermaid
48
+ flowchart LR
49
+ default[default.env] --> prod[prod.env]
50
+ prod --> dev[dev.env]
51
+ prod --> test[test.env]
52
+ ```
53
+
54
+ Later layers override earlier ones. Use envstack -t VAR to trace where a value
55
+ comes from. envstack focuses on **configuration and activation**, not dependency
56
+ resolution.
57
+
58
+ For the core concepts, see
59
+ [docs/index.md](https://github.com/rsgalloway/envstack/blob/master/docs/index.md).
47
60
 
48
61
  ## Installation
49
62
 
@@ -55,7 +68,9 @@ pip install -U envstack
55
68
 
56
69
  ## Quickstart
57
70
 
58
- Start by getting the latest `default.env` example file:
71
+ Start by getting the latest
72
+ [default.env](https://github.com/rsgalloway/envstack/blob/master/examples/default/default.env)
73
+ example file:
59
74
 
60
75
  ```bash
61
76
  curl -o \
@@ -79,7 +94,6 @@ $ envstack -u
79
94
  DEPLOY_ROOT=${ROOT}/${ENV}
80
95
  ENV=prod
81
96
  ENVPATH=${DEPLOY_ROOT}/env:${ENVPATH}
82
- HELLO=${HELLO:=world}
83
97
  LOG_LEVEL=${LOG_LEVEL:=INFO}
84
98
  PATH=${DEPLOY_ROOT}/bin:${PATH}
85
99
  PS1=\[\e[32m\](${ENV})\[\e[0m\] \w\$
@@ -138,10 +152,22 @@ $ envstack -- node index.js
138
152
  Hello prod
139
153
  ```
140
154
 
155
+ ## Secrets and encryption
156
+
157
+ envstack supports optional encryption of environment values when writing
158
+ environment files, allowing sensitive configuration to be safely stored,
159
+ committed, or distributed.
160
+
161
+ Encryption protects values **at rest** and integrates with environment stacks and
162
+ includes. envstack does not attempt to be a full secret management system.
163
+
164
+ See [docs/secrets.md](https://github.com/rsgalloway/envstack/blob/master/docs/secrets.md) for details.
165
+
141
166
  ## Documentation
142
167
 
143
- - Design & philosophy → `docs/design.md`
144
- - Examples & patterns → `docs/examples.md`
145
- - Tool comparisons → `docs/comparison.md`
146
- - FAQ & gotchas → `docs/faq.md`
147
- - API docs → `docs/api.md`
168
+ - [Design & philosophy](https://github.com/rsgalloway/envstack/blob/master/docs/design.md)
169
+ - [Examples & patterns](https://github.com/rsgalloway/envstack/blob/master/docs/examples.md)
170
+ - [Tool comparisons](https://github.com/rsgalloway/envstack/blob/master/docs/comparison.md)
171
+ - [Secrets and encryption](https://github.com/rsgalloway/envstack/blob/master/docs/secrets.md)
172
+ - [FAQ & gotchas](https://github.com/rsgalloway/envstack/blob/master/docs/faq.md)
173
+ - [API docs](https://github.com/rsgalloway/envstack/blob/master/docs/api.md)
@@ -13,9 +13,22 @@ Environment variable composition and activation layer for tools and processes.
13
13
  - Shared, policy-driven environments
14
14
  - Inspectable and deterministic behavior
15
15
 
16
- envstack focuses on **configuration and activation**, not dependency resolution.
16
+ envstack environments are layered hierarchically, with later layers inheriting
17
+ from and overriding earlier ones.
18
+
19
+ ```mermaid
20
+ flowchart LR
21
+ default[default.env] --> prod[prod.env]
22
+ prod --> dev[dev.env]
23
+ prod --> test[test.env]
24
+ ```
25
+
26
+ Later layers override earlier ones. Use envstack -t VAR to trace where a value
27
+ comes from. envstack focuses on **configuration and activation**, not dependency
28
+ resolution.
17
29
 
18
- For the core concepts, see `docs/index.md`.
30
+ For the core concepts, see
31
+ [docs/index.md](https://github.com/rsgalloway/envstack/blob/master/docs/index.md).
19
32
 
20
33
  ## Installation
21
34
 
@@ -27,7 +40,9 @@ pip install -U envstack
27
40
 
28
41
  ## Quickstart
29
42
 
30
- Start by getting the latest `default.env` example file:
43
+ Start by getting the latest
44
+ [default.env](https://github.com/rsgalloway/envstack/blob/master/examples/default/default.env)
45
+ example file:
31
46
 
32
47
  ```bash
33
48
  curl -o \
@@ -51,7 +66,6 @@ $ envstack -u
51
66
  DEPLOY_ROOT=${ROOT}/${ENV}
52
67
  ENV=prod
53
68
  ENVPATH=${DEPLOY_ROOT}/env:${ENVPATH}
54
- HELLO=${HELLO:=world}
55
69
  LOG_LEVEL=${LOG_LEVEL:=INFO}
56
70
  PATH=${DEPLOY_ROOT}/bin:${PATH}
57
71
  PS1=\[\e[32m\](${ENV})\[\e[0m\] \w\$
@@ -110,10 +124,22 @@ $ envstack -- node index.js
110
124
  Hello prod
111
125
  ```
112
126
 
127
+ ## Secrets and encryption
128
+
129
+ envstack supports optional encryption of environment values when writing
130
+ environment files, allowing sensitive configuration to be safely stored,
131
+ committed, or distributed.
132
+
133
+ Encryption protects values **at rest** and integrates with environment stacks and
134
+ includes. envstack does not attempt to be a full secret management system.
135
+
136
+ See [docs/secrets.md](https://github.com/rsgalloway/envstack/blob/master/docs/secrets.md) for details.
137
+
113
138
  ## Documentation
114
139
 
115
- - Design & philosophy → `docs/design.md`
116
- - Examples & patterns → `docs/examples.md`
117
- - Tool comparisons → `docs/comparison.md`
118
- - FAQ & gotchas → `docs/faq.md`
119
- - API docs → `docs/api.md`
140
+ - [Design & philosophy](https://github.com/rsgalloway/envstack/blob/master/docs/design.md)
141
+ - [Examples & patterns](https://github.com/rsgalloway/envstack/blob/master/docs/examples.md)
142
+ - [Tool comparisons](https://github.com/rsgalloway/envstack/blob/master/docs/comparison.md)
143
+ - [Secrets and encryption](https://github.com/rsgalloway/envstack/blob/master/docs/secrets.md)
144
+ - [FAQ & gotchas](https://github.com/rsgalloway/envstack/blob/master/docs/faq.md)
145
+ - [API docs](https://github.com/rsgalloway/envstack/blob/master/docs/api.md)
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env python3
2
2
  #
3
- # Copyright (c) 2024-2025, Ryan Galloway (ryan@rsgalloway.com)
3
+ # Copyright (c) 2024-2026, Ryan Galloway (ryan@rsgalloway.com)
4
4
  #
5
5
  # Redistribution and use in source and binary forms, with or without
6
6
  # modification, are permitted provided that the following conditions are met:
@@ -34,7 +34,7 @@ Stacked environment variable management system.
34
34
  """
35
35
 
36
36
  __prog__ = "envstack"
37
- __version__ = "0.9.6"
37
+ __version__ = "1.0.1"
38
38
 
39
39
  from envstack.env import clear, init, revert, save # noqa: F401
40
- from envstack.env import load_environ, resolve_environ # noqa: F401
40
+ from envstack.env import load_environ, resolve_environ # noqa: F401
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env python3
2
2
  #
3
- # Copyright (c) 2024-2025, Ryan Galloway (ryan@rsgalloway.com)
3
+ # Copyright (c) 2024-2026, Ryan Galloway (ryan@rsgalloway.com)
4
4
  #
5
5
  # Redistribution and use in source and binary forms, with or without
6
6
  # modification, are permitted provided that the following conditions are met:
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env python3
2
2
  #
3
- # Copyright (c) 2024-2025, Ryan Galloway (ryan@rsgalloway.com)
3
+ # Copyright (c) 2024-2026, Ryan Galloway (ryan@rsgalloway.com)
4
4
  #
5
5
  # Redistribution and use in source and binary forms, with or without
6
6
  # modification, are permitted provided that the following conditions are met:
@@ -57,12 +57,32 @@ def detect_shell():
57
57
  return "/usr/bin/bash"
58
58
 
59
59
 
60
+ # debug mode
60
61
  DEBUG = os.getenv("DEBUG")
62
+
63
+ # default namespace
61
64
  DEFAULT_NAMESPACE = os.getenv("DEFAULT_ENV_STACK", "default")
65
+
66
+ # allow embedded commands
67
+ ALLOW_COMMANDS = os.getenv("ALLOW_COMMANDS", "0") in ("1", "true", "True", "TRUE")
68
+
69
+ # embedded command timeout in seconds
70
+ try:
71
+ COMMAND_TIMEOUT = int(os.getenv("COMMAND_TIMEOUT", 5))
72
+ except ValueError:
73
+ COMMAND_TIMEOUT = 5
74
+
75
+ # default environment variables
62
76
  ENV = os.getenv("ENV", "prod")
63
77
  HOME = os.getenv("HOME")
64
- IGNORE_MISSING = bool(os.getenv("IGNORE_MISSING", 1))
78
+
79
+ # Ignore missing stack files when resolving environments
80
+ IGNORE_MISSING = os.getenv("IGNORE_MISSING", "1") in ("1", "true", "True", "TRUE")
81
+
82
+ # logging level
65
83
  LOG_LEVEL = os.environ.get("LOG_LEVEL", "INFO")
84
+
85
+ # platform and shell info
66
86
  ON_POSIX = "posix" in sys.builtin_module_names
67
87
  PLATFORM = platform.system().lower()
68
88
  PYTHON_VERSION = sys.version_info[0]
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env python3
2
2
  #
3
- # Copyright (c) 2024-2025, Ryan Galloway (ryan@rsgalloway.com)
3
+ # Copyright (c) 2024-2026, Ryan Galloway (ryan@rsgalloway.com)
4
4
  #
5
5
  # Redistribution and use in source and binary forms, with or without
6
6
  # modification, are permitted provided that the following conditions are met:
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env python3
2
2
  #
3
- # Copyright (c) 2024-2025, Ryan Galloway (ryan@rsgalloway.com)
3
+ # Copyright (c) 2024-2026, Ryan Galloway (ryan@rsgalloway.com)
4
4
  #
5
5
  # Redistribution and use in source and binary forms, with or without
6
6
  # modification, are permitted provided that the following conditions are met:
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env python3
2
2
  #
3
- # Copyright (c) 2024-2025, Ryan Galloway (ryan@rsgalloway.com)
3
+ # Copyright (c) 2024-2026, Ryan Galloway (ryan@rsgalloway.com)
4
4
  #
5
5
  # Redistribution and use in source and binary forms, with or without
6
6
  # modification, are permitted provided that the following conditions are met:
@@ -131,7 +131,7 @@ class EnvshellWrapper(Wrapper):
131
131
  if os.name == "nt":
132
132
  return ("PROMPT", "$E[32m(${ENV:=${STACK}})$E[0m $P$G ")
133
133
  else:
134
- return ("PS1", "\[\e[32m\](${ENV:=${STACK}})\[\e[0m\] \w\$ ")
134
+ return ("PS1", r"\[\e[32m\](${ENV:=${STACK}})\[\e[0m\] \w\$ ")
135
135
 
136
136
  def get_subprocess_env(self):
137
137
  """
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env python3
2
2
  #
3
- # Copyright (c) 2024-2025, Ryan Galloway (ryan@rsgalloway.com)
3
+ # Copyright (c) 2024-2026, Ryan Galloway (ryan@rsgalloway.com)
4
4
  #
5
5
  # Redistribution and use in source and binary forms, with or without
6
6
  # modification, are permitted provided that the following conditions are met:
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env python3
2
2
  #
3
- # Copyright (c) 2024-2025, Ryan Galloway (ryan@rsgalloway.com)
3
+ # Copyright (c) 2024-2026, Ryan Galloway (ryan@rsgalloway.com)
4
4
  #
5
5
  # Redistribution and use in source and binary forms, with or without
6
6
  # modification, are permitted provided that the following conditions are met:
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env python3
2
2
  #
3
- # Copyright (c) 2024-2025, Ryan Galloway (ryan@rsgalloway.com)
3
+ # Copyright (c) 2024-2026, Ryan Galloway (ryan@rsgalloway.com)
4
4
  #
5
5
  # Redistribution and use in source and binary forms, with or without
6
6
  # modification, are permitted provided that the following conditions are met:
@@ -250,30 +250,35 @@ class FernetNode(BaseNode):
250
250
 
251
251
 
252
252
  class CustomLoader(yaml.SafeLoader):
253
+ """Custom Loader class to preserve order of keys and ensure required
254
+ keys are first in the mapping."""
255
+
253
256
  required_keys = {"include", "all", "darwin", "linux", "windows"}
254
257
 
255
258
  def construct_mapping(self, node: yaml.Node, deep: bool = False):
256
- mapping = super().construct_mapping(node, deep=deep)
257
- for key, value in mapping.items():
259
+ """Construct a mapping from a YAML node, preserving the order of keys
260
+ and ensuring"""
261
+ # keep YAML merge keys (<<) working
262
+ self.flatten_mapping(node)
263
+
264
+ mapping = {}
265
+
266
+ for key_node, value_node in node.value:
267
+ # never implicit-resolve scalar KEYS (YES/NO/ON/OFF/null/date/etc)
268
+ if isinstance(key_node, yaml.ScalarNode):
269
+ key = key_node.value
270
+ else:
271
+ key = self.construct_object(key_node, deep=deep)
272
+
273
+ value = self.construct_object(value_node, deep=deep)
274
+ mapping[key] = value
275
+
276
+ # preserve order of keys and ensure required keys are first in the mapping
277
+ for key, value in list(mapping.items()):
258
278
  if key in self.required_keys:
259
279
  continue
260
- try:
261
- if node.tag == Base64Node.yaml_tag:
262
- mapping[key] = Base64Node(value)
263
- elif node.tag == EncryptedNode.yaml_tag:
264
- mapping[key] = EncryptedNode(value)
265
- elif node.tag == AESGCMNode.yaml_tag:
266
- mapping[key] = AESGCMNode(value)
267
- elif node.tag == FernetNode.yaml_tag:
268
- mapping[key] = FernetNode(value)
269
- elif node.tag == MD5Node.yaml_tag:
270
- mapping[key] = MD5Node(value)
271
- else:
272
- mapping[key] = Template(value)
273
- except Exception as e:
274
- raise yaml.constructor.ConstructorError(
275
- None, None, f"Error parsing template: {e}", node.start_mark
276
- )
280
+ mapping[key] = value
281
+
277
282
  return mapping
278
283
 
279
284