robotframework-env-manage-library 0.1.0__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.
- robotframework_env_manage_library-0.1.0/LICENSE +21 -0
- robotframework_env_manage_library-0.1.0/PKG-INFO +162 -0
- robotframework_env_manage_library-0.1.0/README.md +138 -0
- robotframework_env_manage_library-0.1.0/pyproject.toml +36 -0
- robotframework_env_manage_library-0.1.0/setup.cfg +4 -0
- robotframework_env_manage_library-0.1.0/src/EnvManageLibrary/__init__.py +6 -0
- robotframework_env_manage_library-0.1.0/src/EnvManageLibrary/env_manage_library.py +140 -0
- robotframework_env_manage_library-0.1.0/src/robotframework_env_manage_library.egg-info/PKG-INFO +162 -0
- robotframework_env_manage_library-0.1.0/src/robotframework_env_manage_library.egg-info/SOURCES.txt +11 -0
- robotframework_env_manage_library-0.1.0/src/robotframework_env_manage_library.egg-info/dependency_links.txt +1 -0
- robotframework_env_manage_library-0.1.0/src/robotframework_env_manage_library.egg-info/requires.txt +4 -0
- robotframework_env_manage_library-0.1.0/src/robotframework_env_manage_library.egg-info/top_level.txt +1 -0
- robotframework_env_manage_library-0.1.0/tests/test_env_manage_library.py +124 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 EnvManageLibrary Contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: robotframework-env-manage-library
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Robot Framework library to override test variables from .env files
|
|
5
|
+
License-Expression: MIT
|
|
6
|
+
Project-URL: Repository, https://github.com/Non-Spk/robotframework-env-manage-library
|
|
7
|
+
Project-URL: Issues, https://github.com/Non-Spk/robotframework-env-manage-library/issues
|
|
8
|
+
Keywords: robotframework,env,testing,yaml,dotenv
|
|
9
|
+
Classifier: Framework :: Robot Framework
|
|
10
|
+
Classifier: Framework :: Robot Framework :: Library
|
|
11
|
+
Classifier: Programming Language :: Python :: 3
|
|
12
|
+
Classifier: Operating System :: OS Independent
|
|
13
|
+
Classifier: Operating System :: Microsoft :: Windows
|
|
14
|
+
Classifier: Operating System :: POSIX :: Linux
|
|
15
|
+
Classifier: Operating System :: MacOS
|
|
16
|
+
Classifier: Topic :: Software Development :: Testing
|
|
17
|
+
Requires-Python: >=3.8
|
|
18
|
+
Description-Content-Type: text/markdown
|
|
19
|
+
License-File: LICENSE
|
|
20
|
+
Requires-Dist: robotframework>=5.0
|
|
21
|
+
Provides-Extra: dev
|
|
22
|
+
Requires-Dist: pytest>=7.0; extra == "dev"
|
|
23
|
+
Dynamic: license-file
|
|
24
|
+
|
|
25
|
+
# EnvManageLibrary
|
|
26
|
+
|
|
27
|
+
Robot Framework keyword library that overrides test variables with values from `.env` files.
|
|
28
|
+
|
|
29
|
+
Variables loaded via Robot Framework's `Variables` directive (from YAML files) are automatically matched against `.env` entries and overridden at test-case scope.
|
|
30
|
+
|
|
31
|
+
## Installation
|
|
32
|
+
|
|
33
|
+
### Option 1: From PyPI
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
pip install robotframework-env-manage-library
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
### Option 2: From local .whl / .tar.gz file
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
pip install robotframework_env_manage_library-0.1.0-py3-none-any.whl
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
### Option 3: From local packages directory (offline)
|
|
46
|
+
|
|
47
|
+
```bash
|
|
48
|
+
pip install --no-index --find-links=./packages robotframework-env-manage-library
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
## .env Naming Convention
|
|
52
|
+
|
|
53
|
+
The dot (`.`) separator distinguishes nested dict keys from flat variable names containing underscores.
|
|
54
|
+
|
|
55
|
+
| .env Key | RF Variable | Type |
|
|
56
|
+
|---|---|---|
|
|
57
|
+
| `USER=value` | `${user}` | Flat |
|
|
58
|
+
| `DB_HOST=value` | `${db_host}` | Flat |
|
|
59
|
+
| `AUTHEN.USER=value` | `${authen}[user]` | Nested dict |
|
|
60
|
+
| `DB.CONN.HOST=value` | `${db}[conn][host]` | Deep nested dict |
|
|
61
|
+
|
|
62
|
+
## Usage
|
|
63
|
+
|
|
64
|
+
### YAML data files
|
|
65
|
+
|
|
66
|
+
```yaml
|
|
67
|
+
# data/credentials.yaml
|
|
68
|
+
user: testuser
|
|
69
|
+
pass: "default_pass"
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
```yaml
|
|
73
|
+
# data/config.yaml
|
|
74
|
+
db:
|
|
75
|
+
host: localhost
|
|
76
|
+
port: "5432"
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
### .env file
|
|
80
|
+
|
|
81
|
+
```env
|
|
82
|
+
# Flat overrides
|
|
83
|
+
USER=admin
|
|
84
|
+
PASS=S3cretK3y!
|
|
85
|
+
|
|
86
|
+
# Nested dict overrides (use dot separator)
|
|
87
|
+
DB.HOST=prod-db.example.com
|
|
88
|
+
DB.PORT=5433
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
### Robot Framework test
|
|
92
|
+
|
|
93
|
+
```robot
|
|
94
|
+
*** Settings ***
|
|
95
|
+
Library EnvManageLibrary
|
|
96
|
+
Variables ${CURDIR}/data/credentials.yaml
|
|
97
|
+
Variables ${CURDIR}/data/config.yaml
|
|
98
|
+
|
|
99
|
+
*** Test Cases ***
|
|
100
|
+
Verify Credentials Override
|
|
101
|
+
Override Variables From Env path/to/.env
|
|
102
|
+
Should Be Equal ${user} admin
|
|
103
|
+
Should Be Equal ${pass} S3cretK3y!
|
|
104
|
+
|
|
105
|
+
Verify Config Override
|
|
106
|
+
Override Variables From Env path/to/.env
|
|
107
|
+
Should Be Equal ${db}[host] prod-db.example.com
|
|
108
|
+
Should Be Equal ${db}[port] 5433
|
|
109
|
+
|
|
110
|
+
Values Reset Between Tests
|
|
111
|
+
# No override called - original YAML values are intact
|
|
112
|
+
Should Be Equal ${user} testuser
|
|
113
|
+
Should Be Equal ${db}[host] localhost
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
## How It Works
|
|
117
|
+
|
|
118
|
+
1. Robot Framework loads YAML files via `Variables`, creating RF variables
|
|
119
|
+
2. `Override Variables From Env` parses the `.env` file
|
|
120
|
+
3. For each RF variable, it checks for a matching `.env` key:
|
|
121
|
+
- Flat string variables: YAML key `user` matches env key `USER`
|
|
122
|
+
- Dict variables: YAML key `db` with child `host` matches env key `DB.HOST`
|
|
123
|
+
4. Matched values are overridden using `Set Test Variable` (scoped to current test only)
|
|
124
|
+
5. Original YAML values remain intact for other test cases
|
|
125
|
+
|
|
126
|
+
## Key Behaviors
|
|
127
|
+
|
|
128
|
+
- Override scope is per test case - original values reset between tests
|
|
129
|
+
- If `.env` file doesn't exist, all variables keep their YAML values
|
|
130
|
+
- Case insensitive - YAML `user` matches env `USER`
|
|
131
|
+
- Flat keys with underscores (`DB_HOST`) only override flat RF variables (`${db_host}`), not nested dicts
|
|
132
|
+
- Dict values are deep-copied before override to prevent mutation
|
|
133
|
+
|
|
134
|
+
## Development
|
|
135
|
+
|
|
136
|
+
```bash
|
|
137
|
+
git clone https://github.com/Non-Spk/robotframework-env-manage-library.git
|
|
138
|
+
cd robotframework-env-manage-library
|
|
139
|
+
python -m venv venv
|
|
140
|
+
|
|
141
|
+
# Windows
|
|
142
|
+
venv\Scripts\pip install -e ".[dev]"
|
|
143
|
+
venv\Scripts\pytest -v
|
|
144
|
+
|
|
145
|
+
# macOS / Linux
|
|
146
|
+
source venv/bin/activate
|
|
147
|
+
pip install -e ".[dev]"
|
|
148
|
+
pytest -v
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
## Building
|
|
152
|
+
|
|
153
|
+
```bash
|
|
154
|
+
pip install build
|
|
155
|
+
python -m build
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
This produces `dist/robotframework_env_manage-0.1.0-py3-none-any.whl` and `dist/robotframework_env_manage-0.1.0.tar.gz`.
|
|
159
|
+
|
|
160
|
+
## License
|
|
161
|
+
|
|
162
|
+
MIT
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
# EnvManageLibrary
|
|
2
|
+
|
|
3
|
+
Robot Framework keyword library that overrides test variables with values from `.env` files.
|
|
4
|
+
|
|
5
|
+
Variables loaded via Robot Framework's `Variables` directive (from YAML files) are automatically matched against `.env` entries and overridden at test-case scope.
|
|
6
|
+
|
|
7
|
+
## Installation
|
|
8
|
+
|
|
9
|
+
### Option 1: From PyPI
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
pip install robotframework-env-manage-library
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
### Option 2: From local .whl / .tar.gz file
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
pip install robotframework_env_manage_library-0.1.0-py3-none-any.whl
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
### Option 3: From local packages directory (offline)
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
pip install --no-index --find-links=./packages robotframework-env-manage-library
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
## .env Naming Convention
|
|
28
|
+
|
|
29
|
+
The dot (`.`) separator distinguishes nested dict keys from flat variable names containing underscores.
|
|
30
|
+
|
|
31
|
+
| .env Key | RF Variable | Type |
|
|
32
|
+
|---|---|---|
|
|
33
|
+
| `USER=value` | `${user}` | Flat |
|
|
34
|
+
| `DB_HOST=value` | `${db_host}` | Flat |
|
|
35
|
+
| `AUTHEN.USER=value` | `${authen}[user]` | Nested dict |
|
|
36
|
+
| `DB.CONN.HOST=value` | `${db}[conn][host]` | Deep nested dict |
|
|
37
|
+
|
|
38
|
+
## Usage
|
|
39
|
+
|
|
40
|
+
### YAML data files
|
|
41
|
+
|
|
42
|
+
```yaml
|
|
43
|
+
# data/credentials.yaml
|
|
44
|
+
user: testuser
|
|
45
|
+
pass: "default_pass"
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
```yaml
|
|
49
|
+
# data/config.yaml
|
|
50
|
+
db:
|
|
51
|
+
host: localhost
|
|
52
|
+
port: "5432"
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
### .env file
|
|
56
|
+
|
|
57
|
+
```env
|
|
58
|
+
# Flat overrides
|
|
59
|
+
USER=admin
|
|
60
|
+
PASS=S3cretK3y!
|
|
61
|
+
|
|
62
|
+
# Nested dict overrides (use dot separator)
|
|
63
|
+
DB.HOST=prod-db.example.com
|
|
64
|
+
DB.PORT=5433
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
### Robot Framework test
|
|
68
|
+
|
|
69
|
+
```robot
|
|
70
|
+
*** Settings ***
|
|
71
|
+
Library EnvManageLibrary
|
|
72
|
+
Variables ${CURDIR}/data/credentials.yaml
|
|
73
|
+
Variables ${CURDIR}/data/config.yaml
|
|
74
|
+
|
|
75
|
+
*** Test Cases ***
|
|
76
|
+
Verify Credentials Override
|
|
77
|
+
Override Variables From Env path/to/.env
|
|
78
|
+
Should Be Equal ${user} admin
|
|
79
|
+
Should Be Equal ${pass} S3cretK3y!
|
|
80
|
+
|
|
81
|
+
Verify Config Override
|
|
82
|
+
Override Variables From Env path/to/.env
|
|
83
|
+
Should Be Equal ${db}[host] prod-db.example.com
|
|
84
|
+
Should Be Equal ${db}[port] 5433
|
|
85
|
+
|
|
86
|
+
Values Reset Between Tests
|
|
87
|
+
# No override called - original YAML values are intact
|
|
88
|
+
Should Be Equal ${user} testuser
|
|
89
|
+
Should Be Equal ${db}[host] localhost
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
## How It Works
|
|
93
|
+
|
|
94
|
+
1. Robot Framework loads YAML files via `Variables`, creating RF variables
|
|
95
|
+
2. `Override Variables From Env` parses the `.env` file
|
|
96
|
+
3. For each RF variable, it checks for a matching `.env` key:
|
|
97
|
+
- Flat string variables: YAML key `user` matches env key `USER`
|
|
98
|
+
- Dict variables: YAML key `db` with child `host` matches env key `DB.HOST`
|
|
99
|
+
4. Matched values are overridden using `Set Test Variable` (scoped to current test only)
|
|
100
|
+
5. Original YAML values remain intact for other test cases
|
|
101
|
+
|
|
102
|
+
## Key Behaviors
|
|
103
|
+
|
|
104
|
+
- Override scope is per test case - original values reset between tests
|
|
105
|
+
- If `.env` file doesn't exist, all variables keep their YAML values
|
|
106
|
+
- Case insensitive - YAML `user` matches env `USER`
|
|
107
|
+
- Flat keys with underscores (`DB_HOST`) only override flat RF variables (`${db_host}`), not nested dicts
|
|
108
|
+
- Dict values are deep-copied before override to prevent mutation
|
|
109
|
+
|
|
110
|
+
## Development
|
|
111
|
+
|
|
112
|
+
```bash
|
|
113
|
+
git clone https://github.com/Non-Spk/robotframework-env-manage-library.git
|
|
114
|
+
cd robotframework-env-manage-library
|
|
115
|
+
python -m venv venv
|
|
116
|
+
|
|
117
|
+
# Windows
|
|
118
|
+
venv\Scripts\pip install -e ".[dev]"
|
|
119
|
+
venv\Scripts\pytest -v
|
|
120
|
+
|
|
121
|
+
# macOS / Linux
|
|
122
|
+
source venv/bin/activate
|
|
123
|
+
pip install -e ".[dev]"
|
|
124
|
+
pytest -v
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
## Building
|
|
128
|
+
|
|
129
|
+
```bash
|
|
130
|
+
pip install build
|
|
131
|
+
python -m build
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
This produces `dist/robotframework_env_manage-0.1.0-py3-none-any.whl` and `dist/robotframework_env_manage-0.1.0.tar.gz`.
|
|
135
|
+
|
|
136
|
+
## License
|
|
137
|
+
|
|
138
|
+
MIT
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=68.0", "wheel"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "robotframework-env-manage-library"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "Robot Framework library to override test variables from .env files"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
license = "MIT"
|
|
11
|
+
requires-python = ">=3.8"
|
|
12
|
+
dependencies = ["robotframework>=5.0"]
|
|
13
|
+
keywords = ["robotframework", "env", "testing", "yaml", "dotenv"]
|
|
14
|
+
classifiers = [
|
|
15
|
+
"Framework :: Robot Framework",
|
|
16
|
+
"Framework :: Robot Framework :: Library",
|
|
17
|
+
"Programming Language :: Python :: 3",
|
|
18
|
+
"Operating System :: OS Independent",
|
|
19
|
+
"Operating System :: Microsoft :: Windows",
|
|
20
|
+
"Operating System :: POSIX :: Linux",
|
|
21
|
+
"Operating System :: MacOS",
|
|
22
|
+
"Topic :: Software Development :: Testing",
|
|
23
|
+
]
|
|
24
|
+
|
|
25
|
+
[project.optional-dependencies]
|
|
26
|
+
dev = ["pytest>=7.0"]
|
|
27
|
+
|
|
28
|
+
[project.urls]
|
|
29
|
+
Repository = "https://github.com/Non-Spk/robotframework-env-manage-library"
|
|
30
|
+
Issues = "https://github.com/Non-Spk/robotframework-env-manage-library/issues"
|
|
31
|
+
|
|
32
|
+
[tool.setuptools.packages.find]
|
|
33
|
+
where = ["src"]
|
|
34
|
+
|
|
35
|
+
[tool.pytest.ini_options]
|
|
36
|
+
testpaths = ["tests"]
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
"""
|
|
2
|
+
EnvManageLibrary - Robot Framework keyword library.
|
|
3
|
+
|
|
4
|
+
Scans existing RF variables and overrides their values with matching
|
|
5
|
+
entries from a .env file, scoped to the current test case.
|
|
6
|
+
|
|
7
|
+
.env naming convention:
|
|
8
|
+
- Flat variable ``${user}`` -> ``USER=value``
|
|
9
|
+
- Flat variable ``${authen_key_user}`` -> ``AUTHEN_KEY_USER=value``
|
|
10
|
+
- Nested dict ``${authen_key}[user]`` -> ``AUTHEN_KEY.USER=value``
|
|
11
|
+
|
|
12
|
+
The dot (``.``) separator distinguishes nested dict keys from flat
|
|
13
|
+
variable names that contain underscores.
|
|
14
|
+
|
|
15
|
+
Usage in Robot Framework:
|
|
16
|
+
Library EnvManageLibrary
|
|
17
|
+
Variables ${CURDIR}/data/authen.yaml
|
|
18
|
+
|
|
19
|
+
EnvManageLibrary.Override Variables From Env .env
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
import copy
|
|
23
|
+
from pathlib import Path
|
|
24
|
+
from robot.api.deco import keyword
|
|
25
|
+
from robot.libraries.BuiltIn import BuiltIn
|
|
26
|
+
|
|
27
|
+
_SKIP_VARS = frozenset((
|
|
28
|
+
"None", "True", "False", "null", "true", "false",
|
|
29
|
+
"EMPTY", "SPACE", "\\n",
|
|
30
|
+
))
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def _load_dotenv(env_path):
|
|
34
|
+
"""Parse a .env file into a dict."""
|
|
35
|
+
env_vars = {}
|
|
36
|
+
path = Path(env_path)
|
|
37
|
+
if not path.is_file():
|
|
38
|
+
return env_vars
|
|
39
|
+
for line in path.read_text(encoding="utf-8").splitlines():
|
|
40
|
+
line = line.strip()
|
|
41
|
+
if not line or line.startswith("#"):
|
|
42
|
+
continue
|
|
43
|
+
if "=" not in line:
|
|
44
|
+
continue
|
|
45
|
+
key, _, value = line.partition("=")
|
|
46
|
+
env_vars[key.strip()] = value.strip().strip("'\"")
|
|
47
|
+
return env_vars
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def _build_nested_overrides(env_vars):
|
|
51
|
+
"""Build a lookup for nested dict overrides from dot-notation env keys.
|
|
52
|
+
|
|
53
|
+
Returns a dict like: ``{"authen_key": {"user": "val", "pass": "val"}}``
|
|
54
|
+
from env keys like ``AUTHEN_KEY.USER=val``, ``AUTHEN_KEY.PASS=val``.
|
|
55
|
+
"""
|
|
56
|
+
nested = {}
|
|
57
|
+
for env_key, env_value in env_vars.items():
|
|
58
|
+
if "." not in env_key:
|
|
59
|
+
continue
|
|
60
|
+
parts = env_key.split(".")
|
|
61
|
+
var_name = parts[0].lower()
|
|
62
|
+
remaining = [p.lower() for p in parts[1:]]
|
|
63
|
+
|
|
64
|
+
current = nested.setdefault(var_name, {})
|
|
65
|
+
for part in remaining[:-1]:
|
|
66
|
+
current = current.setdefault(part, {})
|
|
67
|
+
current[remaining[-1]] = env_value
|
|
68
|
+
return nested
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def _apply_nested_overrides(data, overrides):
|
|
72
|
+
"""Apply nested overrides to a dict. Returns True if anything changed."""
|
|
73
|
+
changed = False
|
|
74
|
+
for key in data:
|
|
75
|
+
if key not in overrides:
|
|
76
|
+
continue
|
|
77
|
+
override = overrides[key]
|
|
78
|
+
if isinstance(data[key], dict) and isinstance(override, dict):
|
|
79
|
+
if _apply_nested_overrides(data[key], override):
|
|
80
|
+
changed = True
|
|
81
|
+
else:
|
|
82
|
+
data[key] = override
|
|
83
|
+
changed = True
|
|
84
|
+
return changed
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
class EnvManageLibrary:
|
|
88
|
+
"""Robot Framework library for overriding variables from .env files."""
|
|
89
|
+
|
|
90
|
+
ROBOT_LIBRARY_SCOPE = "GLOBAL"
|
|
91
|
+
|
|
92
|
+
@keyword("Override Variables From Env")
|
|
93
|
+
def override_variables_from_env(self, env_path=".env"):
|
|
94
|
+
"""Override current RF variables with values from a .env file.
|
|
95
|
+
|
|
96
|
+
Naming convention in .env:
|
|
97
|
+
- ``USER=value`` -> overrides flat ``${user}``
|
|
98
|
+
- ``AUTHEN_KEY_USER=value`` -> overrides flat ``${authen_key_user}``
|
|
99
|
+
- ``AUTHEN_KEY.USER=value`` -> overrides nested ``${authen_key}[user]``
|
|
100
|
+
|
|
101
|
+
Overrides are scoped to the current test case (``Set Test Variable``).
|
|
102
|
+
If .env file is missing, nothing changes.
|
|
103
|
+
"""
|
|
104
|
+
builtin = BuiltIn()
|
|
105
|
+
dot_env = _load_dotenv(env_path)
|
|
106
|
+
|
|
107
|
+
if not dot_env:
|
|
108
|
+
return
|
|
109
|
+
|
|
110
|
+
flat_env = {k: v for k, v in dot_env.items() if "." not in k}
|
|
111
|
+
nested_overrides = _build_nested_overrides(dot_env)
|
|
112
|
+
|
|
113
|
+
rf_vars = builtin.get_variables()
|
|
114
|
+
|
|
115
|
+
seen = set()
|
|
116
|
+
for rf_name, rf_value in rf_vars.items():
|
|
117
|
+
if len(rf_name) < 4 or rf_name[1] != "{" or not rf_name.endswith("}"):
|
|
118
|
+
continue
|
|
119
|
+
|
|
120
|
+
var_name = rf_name[2:-1]
|
|
121
|
+
if var_name in seen:
|
|
122
|
+
continue
|
|
123
|
+
seen.add(var_name)
|
|
124
|
+
|
|
125
|
+
if var_name.startswith((" ", "/", "{")):
|
|
126
|
+
continue
|
|
127
|
+
if var_name in _SKIP_VARS:
|
|
128
|
+
continue
|
|
129
|
+
if var_name.upper() == var_name and var_name not in flat_env:
|
|
130
|
+
continue
|
|
131
|
+
|
|
132
|
+
if isinstance(rf_value, dict):
|
|
133
|
+
if var_name in nested_overrides:
|
|
134
|
+
new_value = copy.deepcopy(rf_value)
|
|
135
|
+
if _apply_nested_overrides(new_value, nested_overrides[var_name]):
|
|
136
|
+
builtin.set_test_variable(f"${{{var_name}}}", new_value)
|
|
137
|
+
elif isinstance(rf_value, str):
|
|
138
|
+
env_value = flat_env.get(var_name.upper())
|
|
139
|
+
if env_value is not None:
|
|
140
|
+
builtin.set_test_variable(f"${{{var_name}}}", env_value)
|
robotframework_env_manage_library-0.1.0/src/robotframework_env_manage_library.egg-info/PKG-INFO
ADDED
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: robotframework-env-manage-library
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Robot Framework library to override test variables from .env files
|
|
5
|
+
License-Expression: MIT
|
|
6
|
+
Project-URL: Repository, https://github.com/Non-Spk/robotframework-env-manage-library
|
|
7
|
+
Project-URL: Issues, https://github.com/Non-Spk/robotframework-env-manage-library/issues
|
|
8
|
+
Keywords: robotframework,env,testing,yaml,dotenv
|
|
9
|
+
Classifier: Framework :: Robot Framework
|
|
10
|
+
Classifier: Framework :: Robot Framework :: Library
|
|
11
|
+
Classifier: Programming Language :: Python :: 3
|
|
12
|
+
Classifier: Operating System :: OS Independent
|
|
13
|
+
Classifier: Operating System :: Microsoft :: Windows
|
|
14
|
+
Classifier: Operating System :: POSIX :: Linux
|
|
15
|
+
Classifier: Operating System :: MacOS
|
|
16
|
+
Classifier: Topic :: Software Development :: Testing
|
|
17
|
+
Requires-Python: >=3.8
|
|
18
|
+
Description-Content-Type: text/markdown
|
|
19
|
+
License-File: LICENSE
|
|
20
|
+
Requires-Dist: robotframework>=5.0
|
|
21
|
+
Provides-Extra: dev
|
|
22
|
+
Requires-Dist: pytest>=7.0; extra == "dev"
|
|
23
|
+
Dynamic: license-file
|
|
24
|
+
|
|
25
|
+
# EnvManageLibrary
|
|
26
|
+
|
|
27
|
+
Robot Framework keyword library that overrides test variables with values from `.env` files.
|
|
28
|
+
|
|
29
|
+
Variables loaded via Robot Framework's `Variables` directive (from YAML files) are automatically matched against `.env` entries and overridden at test-case scope.
|
|
30
|
+
|
|
31
|
+
## Installation
|
|
32
|
+
|
|
33
|
+
### Option 1: From PyPI
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
pip install robotframework-env-manage-library
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
### Option 2: From local .whl / .tar.gz file
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
pip install robotframework_env_manage_library-0.1.0-py3-none-any.whl
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
### Option 3: From local packages directory (offline)
|
|
46
|
+
|
|
47
|
+
```bash
|
|
48
|
+
pip install --no-index --find-links=./packages robotframework-env-manage-library
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
## .env Naming Convention
|
|
52
|
+
|
|
53
|
+
The dot (`.`) separator distinguishes nested dict keys from flat variable names containing underscores.
|
|
54
|
+
|
|
55
|
+
| .env Key | RF Variable | Type |
|
|
56
|
+
|---|---|---|
|
|
57
|
+
| `USER=value` | `${user}` | Flat |
|
|
58
|
+
| `DB_HOST=value` | `${db_host}` | Flat |
|
|
59
|
+
| `AUTHEN.USER=value` | `${authen}[user]` | Nested dict |
|
|
60
|
+
| `DB.CONN.HOST=value` | `${db}[conn][host]` | Deep nested dict |
|
|
61
|
+
|
|
62
|
+
## Usage
|
|
63
|
+
|
|
64
|
+
### YAML data files
|
|
65
|
+
|
|
66
|
+
```yaml
|
|
67
|
+
# data/credentials.yaml
|
|
68
|
+
user: testuser
|
|
69
|
+
pass: "default_pass"
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
```yaml
|
|
73
|
+
# data/config.yaml
|
|
74
|
+
db:
|
|
75
|
+
host: localhost
|
|
76
|
+
port: "5432"
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
### .env file
|
|
80
|
+
|
|
81
|
+
```env
|
|
82
|
+
# Flat overrides
|
|
83
|
+
USER=admin
|
|
84
|
+
PASS=S3cretK3y!
|
|
85
|
+
|
|
86
|
+
# Nested dict overrides (use dot separator)
|
|
87
|
+
DB.HOST=prod-db.example.com
|
|
88
|
+
DB.PORT=5433
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
### Robot Framework test
|
|
92
|
+
|
|
93
|
+
```robot
|
|
94
|
+
*** Settings ***
|
|
95
|
+
Library EnvManageLibrary
|
|
96
|
+
Variables ${CURDIR}/data/credentials.yaml
|
|
97
|
+
Variables ${CURDIR}/data/config.yaml
|
|
98
|
+
|
|
99
|
+
*** Test Cases ***
|
|
100
|
+
Verify Credentials Override
|
|
101
|
+
Override Variables From Env path/to/.env
|
|
102
|
+
Should Be Equal ${user} admin
|
|
103
|
+
Should Be Equal ${pass} S3cretK3y!
|
|
104
|
+
|
|
105
|
+
Verify Config Override
|
|
106
|
+
Override Variables From Env path/to/.env
|
|
107
|
+
Should Be Equal ${db}[host] prod-db.example.com
|
|
108
|
+
Should Be Equal ${db}[port] 5433
|
|
109
|
+
|
|
110
|
+
Values Reset Between Tests
|
|
111
|
+
# No override called - original YAML values are intact
|
|
112
|
+
Should Be Equal ${user} testuser
|
|
113
|
+
Should Be Equal ${db}[host] localhost
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
## How It Works
|
|
117
|
+
|
|
118
|
+
1. Robot Framework loads YAML files via `Variables`, creating RF variables
|
|
119
|
+
2. `Override Variables From Env` parses the `.env` file
|
|
120
|
+
3. For each RF variable, it checks for a matching `.env` key:
|
|
121
|
+
- Flat string variables: YAML key `user` matches env key `USER`
|
|
122
|
+
- Dict variables: YAML key `db` with child `host` matches env key `DB.HOST`
|
|
123
|
+
4. Matched values are overridden using `Set Test Variable` (scoped to current test only)
|
|
124
|
+
5. Original YAML values remain intact for other test cases
|
|
125
|
+
|
|
126
|
+
## Key Behaviors
|
|
127
|
+
|
|
128
|
+
- Override scope is per test case - original values reset between tests
|
|
129
|
+
- If `.env` file doesn't exist, all variables keep their YAML values
|
|
130
|
+
- Case insensitive - YAML `user` matches env `USER`
|
|
131
|
+
- Flat keys with underscores (`DB_HOST`) only override flat RF variables (`${db_host}`), not nested dicts
|
|
132
|
+
- Dict values are deep-copied before override to prevent mutation
|
|
133
|
+
|
|
134
|
+
## Development
|
|
135
|
+
|
|
136
|
+
```bash
|
|
137
|
+
git clone https://github.com/Non-Spk/robotframework-env-manage-library.git
|
|
138
|
+
cd robotframework-env-manage-library
|
|
139
|
+
python -m venv venv
|
|
140
|
+
|
|
141
|
+
# Windows
|
|
142
|
+
venv\Scripts\pip install -e ".[dev]"
|
|
143
|
+
venv\Scripts\pytest -v
|
|
144
|
+
|
|
145
|
+
# macOS / Linux
|
|
146
|
+
source venv/bin/activate
|
|
147
|
+
pip install -e ".[dev]"
|
|
148
|
+
pytest -v
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
## Building
|
|
152
|
+
|
|
153
|
+
```bash
|
|
154
|
+
pip install build
|
|
155
|
+
python -m build
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
This produces `dist/robotframework_env_manage-0.1.0-py3-none-any.whl` and `dist/robotframework_env_manage-0.1.0.tar.gz`.
|
|
159
|
+
|
|
160
|
+
## License
|
|
161
|
+
|
|
162
|
+
MIT
|
robotframework_env_manage_library-0.1.0/src/robotframework_env_manage_library.egg-info/SOURCES.txt
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
LICENSE
|
|
2
|
+
README.md
|
|
3
|
+
pyproject.toml
|
|
4
|
+
src/EnvManageLibrary/__init__.py
|
|
5
|
+
src/EnvManageLibrary/env_manage_library.py
|
|
6
|
+
src/robotframework_env_manage_library.egg-info/PKG-INFO
|
|
7
|
+
src/robotframework_env_manage_library.egg-info/SOURCES.txt
|
|
8
|
+
src/robotframework_env_manage_library.egg-info/dependency_links.txt
|
|
9
|
+
src/robotframework_env_manage_library.egg-info/requires.txt
|
|
10
|
+
src/robotframework_env_manage_library.egg-info/top_level.txt
|
|
11
|
+
tests/test_env_manage_library.py
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
robotframework_env_manage_library-0.1.0/src/robotframework_env_manage_library.egg-info/top_level.txt
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
EnvManageLibrary
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
"""Unit tests for env_manage_library helper functions."""
|
|
2
|
+
|
|
3
|
+
import sys
|
|
4
|
+
import os
|
|
5
|
+
import pytest
|
|
6
|
+
|
|
7
|
+
sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..", "src"))
|
|
8
|
+
|
|
9
|
+
from EnvManageLibrary.env_manage_library import (
|
|
10
|
+
_load_dotenv,
|
|
11
|
+
_build_nested_overrides,
|
|
12
|
+
_apply_nested_overrides,
|
|
13
|
+
)
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
# ---------------------------------------------------------------------------
|
|
17
|
+
# _load_dotenv
|
|
18
|
+
# ---------------------------------------------------------------------------
|
|
19
|
+
class TestLoadDotenv:
|
|
20
|
+
def test_basic_key_value(self, tmp_path):
|
|
21
|
+
env_file = tmp_path / ".env"
|
|
22
|
+
env_file.write_text("USER=admin\nPASS=secret\n")
|
|
23
|
+
result = _load_dotenv(str(env_file))
|
|
24
|
+
assert result == {"USER": "admin", "PASS": "secret"}
|
|
25
|
+
|
|
26
|
+
def test_strips_quotes(self, tmp_path):
|
|
27
|
+
env_file = tmp_path / ".env"
|
|
28
|
+
env_file.write_text("A='single'\nB=\"double\"\n")
|
|
29
|
+
result = _load_dotenv(str(env_file))
|
|
30
|
+
assert result == {"A": "single", "B": "double"}
|
|
31
|
+
|
|
32
|
+
def test_skips_comments_and_blanks(self, tmp_path):
|
|
33
|
+
env_file = tmp_path / ".env"
|
|
34
|
+
env_file.write_text("# comment\n\n \nKEY=val\n")
|
|
35
|
+
result = _load_dotenv(str(env_file))
|
|
36
|
+
assert result == {"KEY": "val"}
|
|
37
|
+
|
|
38
|
+
def test_skips_lines_without_equals(self, tmp_path):
|
|
39
|
+
env_file = tmp_path / ".env"
|
|
40
|
+
env_file.write_text("NOEQUALS\nKEY=val\n")
|
|
41
|
+
result = _load_dotenv(str(env_file))
|
|
42
|
+
assert result == {"KEY": "val"}
|
|
43
|
+
|
|
44
|
+
def test_missing_file_returns_empty(self):
|
|
45
|
+
result = _load_dotenv("nonexistent.env")
|
|
46
|
+
assert result == {}
|
|
47
|
+
|
|
48
|
+
def test_value_with_equals_sign(self, tmp_path):
|
|
49
|
+
env_file = tmp_path / ".env"
|
|
50
|
+
env_file.write_text("URL=https://example.com?a=1&b=2\n")
|
|
51
|
+
result = _load_dotenv(str(env_file))
|
|
52
|
+
assert result == {"URL": "https://example.com?a=1&b=2"}
|
|
53
|
+
|
|
54
|
+
def test_strips_whitespace(self, tmp_path):
|
|
55
|
+
env_file = tmp_path / ".env"
|
|
56
|
+
env_file.write_text(" KEY = value \n")
|
|
57
|
+
result = _load_dotenv(str(env_file))
|
|
58
|
+
assert result == {"KEY": "value"}
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
# ---------------------------------------------------------------------------
|
|
62
|
+
# _build_nested_overrides
|
|
63
|
+
# ---------------------------------------------------------------------------
|
|
64
|
+
class TestBuildNestedOverrides:
|
|
65
|
+
def test_single_level_dot(self):
|
|
66
|
+
env = {"AUTHEN_KEY.USER": "admin", "AUTHEN_KEY.PASS": "secret"}
|
|
67
|
+
result = _build_nested_overrides(env)
|
|
68
|
+
assert result == {"authen_key": {"user": "admin", "pass": "secret"}}
|
|
69
|
+
|
|
70
|
+
def test_multi_level_dot(self):
|
|
71
|
+
env = {"DB.CONN.HOST": "localhost", "DB.CONN.PORT": "5432"}
|
|
72
|
+
result = _build_nested_overrides(env)
|
|
73
|
+
assert result == {"db": {"conn": {"host": "localhost", "port": "5432"}}}
|
|
74
|
+
|
|
75
|
+
def test_ignores_flat_keys(self):
|
|
76
|
+
env = {"USER": "admin", "AUTHEN_KEY.USER": "nested"}
|
|
77
|
+
result = _build_nested_overrides(env)
|
|
78
|
+
assert "user" not in result
|
|
79
|
+
assert result == {"authen_key": {"user": "nested"}}
|
|
80
|
+
|
|
81
|
+
def test_empty_dict(self):
|
|
82
|
+
assert _build_nested_overrides({}) == {}
|
|
83
|
+
|
|
84
|
+
def test_case_insensitive_lowered(self):
|
|
85
|
+
env = {"MyVar.SubKey": "val"}
|
|
86
|
+
result = _build_nested_overrides(env)
|
|
87
|
+
assert result == {"myvar": {"subkey": "val"}}
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
# ---------------------------------------------------------------------------
|
|
91
|
+
# _apply_nested_overrides
|
|
92
|
+
# ---------------------------------------------------------------------------
|
|
93
|
+
class TestApplyNestedOverrides:
|
|
94
|
+
def test_overrides_matching_keys(self):
|
|
95
|
+
data = {"user": "old", "pass": "old"}
|
|
96
|
+
overrides = {"user": "new", "pass": "new"}
|
|
97
|
+
changed = _apply_nested_overrides(data, overrides)
|
|
98
|
+
assert changed is True
|
|
99
|
+
assert data == {"user": "new", "pass": "new"}
|
|
100
|
+
|
|
101
|
+
def test_no_match_returns_false(self):
|
|
102
|
+
data = {"user": "old"}
|
|
103
|
+
overrides = {"other": "val"}
|
|
104
|
+
changed = _apply_nested_overrides(data, overrides)
|
|
105
|
+
assert changed is False
|
|
106
|
+
assert data == {"user": "old"}
|
|
107
|
+
|
|
108
|
+
def test_nested_dict_override(self):
|
|
109
|
+
data = {"conn": {"host": "old", "port": "old"}}
|
|
110
|
+
overrides = {"conn": {"host": "new"}}
|
|
111
|
+
changed = _apply_nested_overrides(data, overrides)
|
|
112
|
+
assert changed is True
|
|
113
|
+
assert data == {"conn": {"host": "new", "port": "old"}}
|
|
114
|
+
|
|
115
|
+
def test_partial_override_keeps_others(self):
|
|
116
|
+
data = {"user": "old", "email": "old"}
|
|
117
|
+
overrides = {"user": "new"}
|
|
118
|
+
_apply_nested_overrides(data, overrides)
|
|
119
|
+
assert data == {"user": "new", "email": "old"}
|
|
120
|
+
|
|
121
|
+
def test_empty_overrides(self):
|
|
122
|
+
data = {"user": "old"}
|
|
123
|
+
changed = _apply_nested_overrides(data, {})
|
|
124
|
+
assert changed is False
|