robotframework-tablelibrary 0.2.0__tar.gz → 0.3.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_tablelibrary-0.2.0 → robotframework_tablelibrary-0.3.0}/.github/workflows/build.yml +11 -52
- {robotframework_tablelibrary-0.2.0 → robotframework_tablelibrary-0.3.0}/DEVELOPMENT.md +18 -0
- {robotframework_tablelibrary-0.2.0 → robotframework_tablelibrary-0.3.0}/PKG-INFO +15 -2
- {robotframework_tablelibrary-0.2.0 → robotframework_tablelibrary-0.3.0}/README.md +11 -0
- {robotframework_tablelibrary-0.2.0 → robotframework_tablelibrary-0.3.0}/pyproject.toml +20 -0
- {robotframework_tablelibrary-0.2.0 → robotframework_tablelibrary-0.3.0}/src/Tables/__about__.py +1 -1
- {robotframework_tablelibrary-0.2.0 → robotframework_tablelibrary-0.3.0}/src/Tables/__init__.py +12 -6
- {robotframework_tablelibrary-0.2.0 → robotframework_tablelibrary-0.3.0}/src/Tables/general/library_attributes.py +13 -0
- {robotframework_tablelibrary-0.2.0 → robotframework_tablelibrary-0.3.0}/src/Tables/keywords/configuration.py +25 -0
- {robotframework_tablelibrary-0.2.0 → robotframework_tablelibrary-0.3.0}/src/Tables/keywords/getter.py +21 -4
- {robotframework_tablelibrary-0.2.0 → robotframework_tablelibrary-0.3.0}/src/Tables/utils/file_reader.py +19 -5
- {robotframework_tablelibrary-0.2.0 → robotframework_tablelibrary-0.3.0}/src/Tables/utils/file_writer.py +8 -8
- robotframework_tablelibrary-0.3.0/tests/atest/__init__.robot +17 -0
- robotframework_tablelibrary-0.3.0/tests/atest/test_missing_values.robot +31 -0
- {robotframework_tablelibrary-0.2.0 → robotframework_tablelibrary-0.3.0}/tests/atest/test_settings_scopes.robot +24 -0
- robotframework_tablelibrary-0.3.0/tests/atest/testdata/missing_values.csv +3 -0
- {robotframework_tablelibrary-0.2.0 → robotframework_tablelibrary-0.3.0}/tests/utest/helper/helpers.py +3 -1
- {robotframework_tablelibrary-0.2.0 → robotframework_tablelibrary-0.3.0}/tests/utest/keywords/test_keywords_configuration.py +15 -0
- {robotframework_tablelibrary-0.2.0 → robotframework_tablelibrary-0.3.0}/tests/utest/keywords/test_keywords_getter.py +82 -0
- {robotframework_tablelibrary-0.2.0 → robotframework_tablelibrary-0.3.0}/tests/utest/test_tables_init.py +3 -0
- robotframework_tablelibrary-0.3.0/uv.lock +2267 -0
- {robotframework_tablelibrary-0.2.0 → robotframework_tablelibrary-0.3.0}/.github/workflows/release.yml +0 -0
- {robotframework_tablelibrary-0.2.0 → robotframework_tablelibrary-0.3.0}/.gitignore +0 -0
- {robotframework_tablelibrary-0.2.0 → robotframework_tablelibrary-0.3.0}/.pre-commit-config.yaml +0 -0
- {robotframework_tablelibrary-0.2.0 → robotframework_tablelibrary-0.3.0}/LICENSE +0 -0
- {robotframework_tablelibrary-0.2.0 → robotframework_tablelibrary-0.3.0}/create_release.sh +0 -0
- {robotframework_tablelibrary-0.2.0 → robotframework_tablelibrary-0.3.0}/src/Tables/general/__init__.py +0 -0
- {robotframework_tablelibrary-0.2.0 → robotframework_tablelibrary-0.3.0}/src/Tables/keywords/__init__.py +0 -0
- {robotframework_tablelibrary-0.2.0 → robotframework_tablelibrary-0.3.0}/src/Tables/keywords/excel.py +0 -0
- {robotframework_tablelibrary-0.2.0 → robotframework_tablelibrary-0.3.0}/src/Tables/keywords/modifier.py +0 -0
- {robotframework_tablelibrary-0.2.0 → robotframework_tablelibrary-0.3.0}/src/Tables/keywords/writer.py +0 -0
- {robotframework_tablelibrary-0.2.0 → robotframework_tablelibrary-0.3.0}/src/Tables/utils/__init__.py +0 -0
- {robotframework_tablelibrary-0.2.0 → robotframework_tablelibrary-0.3.0}/src/Tables/utils/file_access.py +0 -0
- {robotframework_tablelibrary-0.2.0 → robotframework_tablelibrary-0.3.0}/src/Tables/utils/file_system.py +0 -0
- {robotframework_tablelibrary-0.2.0 → robotframework_tablelibrary-0.3.0}/src/Tables/utils/settings.py +0 -0
- {robotframework_tablelibrary-0.2.0 → robotframework_tablelibrary-0.3.0}/src/Tables/utils/settings_stack.py +0 -0
- {robotframework_tablelibrary-0.2.0 → robotframework_tablelibrary-0.3.0}/tests/__init__.py +0 -0
- {robotframework_tablelibrary-0.2.0 → robotframework_tablelibrary-0.3.0}/tests/atest/test_excel.robot +0 -0
- {robotframework_tablelibrary-0.2.0 → robotframework_tablelibrary-0.3.0}/tests/atest/test_reader.robot +0 -0
- {robotframework_tablelibrary-0.2.0 → robotframework_tablelibrary-0.3.0}/tests/atest/test_settings.robot +0 -0
- {robotframework_tablelibrary-0.2.0 → robotframework_tablelibrary-0.3.0}/tests/atest/test_writer.robot +0 -0
- {robotframework_tablelibrary-0.2.0 → robotframework_tablelibrary-0.3.0}/tests/atest/testdata/example_01.csv +0 -0
- {robotframework_tablelibrary-0.2.0 → robotframework_tablelibrary-0.3.0}/tests/atest/testdata/example_01.txt +0 -0
- {robotframework_tablelibrary-0.2.0 → robotframework_tablelibrary-0.3.0}/tests/atest/testdata/example_02.xlsx +0 -0
- {robotframework_tablelibrary-0.2.0 → robotframework_tablelibrary-0.3.0}/tests/atest/testdata/example_03.parquet +0 -0
- {robotframework_tablelibrary-0.2.0 → robotframework_tablelibrary-0.3.0}/tests/atest/testdata/example_04.csv +0 -0
- {robotframework_tablelibrary-0.2.0 → robotframework_tablelibrary-0.3.0}/tests/atest/testdata/example_04.txt +0 -0
- {robotframework_tablelibrary-0.2.0 → robotframework_tablelibrary-0.3.0}/tests/atest/testdata/example_05.parquet +0 -0
- {robotframework_tablelibrary-0.2.0 → robotframework_tablelibrary-0.3.0}/tests/atest/testdata/example_06.xlsx +0 -0
- {robotframework_tablelibrary-0.2.0 → robotframework_tablelibrary-0.3.0}/tests/atest/testdata/example_07.csv +0 -0
- {robotframework_tablelibrary-0.2.0 → robotframework_tablelibrary-0.3.0}/tests/atest/testdata/example_08.csv +0 -0
- {robotframework_tablelibrary-0.2.0 → robotframework_tablelibrary-0.3.0}/tests/atest/testdata/example_09_streaming.csv +0 -0
- {robotframework_tablelibrary-0.2.0 → robotframework_tablelibrary-0.3.0}/tests/utest/__init__.py +0 -0
- {robotframework_tablelibrary-0.2.0 → robotframework_tablelibrary-0.3.0}/tests/utest/helper/__init__.py +0 -0
- {robotframework_tablelibrary-0.2.0 → robotframework_tablelibrary-0.3.0}/tests/utest/keywords/__init__.py +0 -0
- {robotframework_tablelibrary-0.2.0 → robotframework_tablelibrary-0.3.0}/tests/utest/keywords/conftest.py +0 -0
- {robotframework_tablelibrary-0.2.0 → robotframework_tablelibrary-0.3.0}/tests/utest/keywords/test_keywords_excel.py +0 -0
- {robotframework_tablelibrary-0.2.0 → robotframework_tablelibrary-0.3.0}/tests/utest/keywords/test_keywords_init.py +0 -0
- {robotframework_tablelibrary-0.2.0 → robotframework_tablelibrary-0.3.0}/tests/utest/keywords/test_keywords_modifier.py +0 -0
- {robotframework_tablelibrary-0.2.0 → robotframework_tablelibrary-0.3.0}/tests/utest/keywords/test_keywords_writer.py +0 -0
- {robotframework_tablelibrary-0.2.0 → robotframework_tablelibrary-0.3.0}/tests/utest/test_configuration.py +0 -0
- {robotframework_tablelibrary-0.2.0 → robotframework_tablelibrary-0.3.0}/tests/utest/test_getter.py +0 -0
- {robotframework_tablelibrary-0.2.0 → robotframework_tablelibrary-0.3.0}/tests/utest/test_utils_file_access.py +0 -0
- {robotframework_tablelibrary-0.2.0 → robotframework_tablelibrary-0.3.0}/tests/utest/test_utils_file_reader.py +0 -0
- {robotframework_tablelibrary-0.2.0 → robotframework_tablelibrary-0.3.0}/tests/utest/test_utils_file_system.py +0 -0
- {robotframework_tablelibrary-0.2.0 → robotframework_tablelibrary-0.3.0}/tests/utest/test_utils_file_writer.py +0 -0
- {robotframework_tablelibrary-0.2.0 → robotframework_tablelibrary-0.3.0}/tests/utest/test_utils_init.py +0 -0
- {robotframework_tablelibrary-0.2.0 → robotframework_tablelibrary-0.3.0}/tests/utest/test_utils_settings.py +0 -0
- {robotframework_tablelibrary-0.2.0 → robotframework_tablelibrary-0.3.0}/tests/utest/test_utils_settings_stack.py +0 -0
- {robotframework_tablelibrary-0.2.0 → robotframework_tablelibrary-0.3.0}/utils/imbus-web-logo.svg +0 -0
{robotframework_tablelibrary-0.2.0 → robotframework_tablelibrary-0.3.0}/.github/workflows/build.yml
RENAMED
|
@@ -40,7 +40,8 @@ jobs:
|
|
|
40
40
|
runs-on: ubuntu-latest
|
|
41
41
|
strategy:
|
|
42
42
|
matrix:
|
|
43
|
-
python-version: ["3.10", "3.11", "3.12"]
|
|
43
|
+
python-version: ["3.10", "3.11", "3.12", "3.13", "3.14"]
|
|
44
|
+
robotframework: ["7.0", "7.4.2"]
|
|
44
45
|
steps:
|
|
45
46
|
- uses: actions/checkout@v4
|
|
46
47
|
|
|
@@ -53,64 +54,22 @@ jobs:
|
|
|
53
54
|
run: pip install hatch
|
|
54
55
|
|
|
55
56
|
- name: Install dependencies
|
|
56
|
-
run: hatch env create
|
|
57
|
-
|
|
58
|
-
- name: Build wheel with Hatch
|
|
59
|
-
run: hatch build
|
|
60
|
-
|
|
61
|
-
- name: Install built wheel
|
|
62
|
-
run: pip install dist/*.whl
|
|
63
|
-
|
|
64
|
-
- name: Test import
|
|
65
|
-
run: python -c "import Tables"
|
|
57
|
+
run: hatch env create test.py${{ matrix.python-version }}-${{ matrix.robotframework }}
|
|
66
58
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
steps:
|
|
71
|
-
- name: Checkout code
|
|
72
|
-
uses: actions/checkout@v4
|
|
59
|
+
- name: Show installed Python version
|
|
60
|
+
run: hatch run test.py${{ matrix.python-version }}-${{ matrix.robotframework }}:python --version
|
|
73
61
|
|
|
74
|
-
- name:
|
|
75
|
-
|
|
76
|
-
with:
|
|
77
|
-
python-version: '3.12'
|
|
78
|
-
|
|
79
|
-
- name: Install Hatch
|
|
80
|
-
run: pip install hatch
|
|
81
|
-
|
|
82
|
-
- name: Install dependencies
|
|
83
|
-
run: hatch env create dev
|
|
62
|
+
- name: Show installed Robot Framework version
|
|
63
|
+
run: hatch run test.py${{ matrix.python-version }}-${{ matrix.robotframework }}:robot --version || test $? -eq 251
|
|
84
64
|
|
|
85
65
|
- name: Build wheel with Hatch
|
|
86
66
|
run: hatch build
|
|
87
67
|
|
|
88
|
-
- name: Run
|
|
89
|
-
run: hatch run
|
|
90
|
-
|
|
91
|
-
run-unit-tests:
|
|
92
|
-
runs-on: ubuntu-latest
|
|
93
|
-
|
|
94
|
-
steps:
|
|
95
|
-
- name: Checkout code
|
|
96
|
-
uses: actions/checkout@v4
|
|
97
|
-
|
|
98
|
-
- name: Set up Python
|
|
99
|
-
uses: actions/setup-python@v5
|
|
100
|
-
with:
|
|
101
|
-
python-version: '3.12'
|
|
102
|
-
|
|
103
|
-
- name: Install Hatch
|
|
104
|
-
run: pip install hatch
|
|
105
|
-
|
|
106
|
-
- name: Install dependencies
|
|
107
|
-
run: hatch env create dev
|
|
108
|
-
|
|
109
|
-
- name: Build wheel with Hatch
|
|
110
|
-
run: hatch build
|
|
68
|
+
- name: Run unit tests
|
|
69
|
+
run: hatch run test.py${{ matrix.python-version }}-${{ matrix.robotframework }}:utest
|
|
111
70
|
|
|
112
|
-
- name: Run
|
|
113
|
-
run: hatch run
|
|
71
|
+
- name: Run acceptance tests
|
|
72
|
+
run: hatch run test.py${{ matrix.python-version }}-${{ matrix.robotframework }}:atest
|
|
114
73
|
|
|
115
74
|
generate-libdoc-docs:
|
|
116
75
|
runs-on: ubuntu-latest
|
|
@@ -97,6 +97,24 @@ hatch run dev:atest
|
|
|
97
97
|
hatch run dev:utest
|
|
98
98
|
```
|
|
99
99
|
|
|
100
|
+
### Execute Tests Against the Version Matrix
|
|
101
|
+
|
|
102
|
+
Install Python 3.10 through 3.14 locally and make sure Hatch can find these interpreters. The test matrix covers Python 3.10-3.14 and Robot Framework 7.0 and 7.4.2.
|
|
103
|
+
|
|
104
|
+
Run all unit and acceptance tests against every matrix combination:
|
|
105
|
+
|
|
106
|
+
```shell
|
|
107
|
+
hatch run test:all
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
To test one combination only, for example Python 3.14 with Robot Framework 7.4.2:
|
|
111
|
+
|
|
112
|
+
```shell
|
|
113
|
+
hatch run test.py3.14-7.4.2:all
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
Use `hatch env show` to list the generated matrix environments.
|
|
117
|
+
|
|
100
118
|
### Generate Docs via libdoc
|
|
101
119
|
|
|
102
120
|
```shell
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
2
|
Name: robotframework-tablelibrary
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.3.0
|
|
4
4
|
Summary: A Robot Framework library providing generic table keywords for several table file type like csv or excel.
|
|
5
5
|
Project-URL: Homepage, https://github.com/imbus/robotframework-tablelibrary
|
|
6
6
|
Project-URL: Documentation, https://github.com/imbus/robotframework-tablelibrary#readme
|
|
@@ -16,6 +16,8 @@ Classifier: Programming Language :: Python
|
|
|
16
16
|
Classifier: Programming Language :: Python :: 3.10
|
|
17
17
|
Classifier: Programming Language :: Python :: 3.11
|
|
18
18
|
Classifier: Programming Language :: Python :: 3.12
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
19
21
|
Requires-Python: >=3.10
|
|
20
22
|
Requires-Dist: openpyxl
|
|
21
23
|
Requires-Dist: overrides
|
|
@@ -110,6 +112,17 @@ ${result} = BuiltIn.Evaluate "index" not in "${content}"
|
|
|
110
112
|
BuiltIn.Should Be True ${result}
|
|
111
113
|
```
|
|
112
114
|
|
|
115
|
+
### Missing values
|
|
116
|
+
|
|
117
|
+
Missing values are returned using pandas' default values by default. To return parsed missing values as Python `None` in list, dictionary, cell, row, and column results, enable the setting. This includes pandas missing-value types such as `NaN`, `pd.NA`, and `NaT`, as well as values recognized as missing by the active parser, such as `N/A` and `NULL`:
|
|
118
|
+
|
|
119
|
+
```
|
|
120
|
+
Tables.Configure Missing As None True
|
|
121
|
+
${content} = Tables.Read Table ${CURDIR}${/}testdata${/}example_01.csv
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
DataFrame results and the library's internal DataFrames are not changed by this setting.
|
|
125
|
+
|
|
113
126
|
### File Format - Parquet
|
|
114
127
|
```
|
|
115
128
|
${content} = Tables.Read Table ${CURDIR}${/}testdata${/}example_05.parquet
|
|
@@ -73,6 +73,17 @@ ${result} = BuiltIn.Evaluate "index" not in "${content}"
|
|
|
73
73
|
BuiltIn.Should Be True ${result}
|
|
74
74
|
```
|
|
75
75
|
|
|
76
|
+
### Missing values
|
|
77
|
+
|
|
78
|
+
Missing values are returned using pandas' default values by default. To return parsed missing values as Python `None` in list, dictionary, cell, row, and column results, enable the setting. This includes pandas missing-value types such as `NaN`, `pd.NA`, and `NaT`, as well as values recognized as missing by the active parser, such as `N/A` and `NULL`:
|
|
79
|
+
|
|
80
|
+
```
|
|
81
|
+
Tables.Configure Missing As None True
|
|
82
|
+
${content} = Tables.Read Table ${CURDIR}${/}testdata${/}example_01.csv
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
DataFrame results and the library's internal DataFrames are not changed by this setting.
|
|
86
|
+
|
|
76
87
|
### File Format - Parquet
|
|
77
88
|
```
|
|
78
89
|
${content} = Tables.Read Table ${CURDIR}${/}testdata${/}example_05.parquet
|
|
@@ -21,6 +21,8 @@ classifiers = [
|
|
|
21
21
|
"Programming Language :: Python :: 3.10",
|
|
22
22
|
"Programming Language :: Python :: 3.11",
|
|
23
23
|
"Programming Language :: Python :: 3.12",
|
|
24
|
+
"Programming Language :: Python :: 3.13",
|
|
25
|
+
"Programming Language :: Python :: 3.14",
|
|
24
26
|
]
|
|
25
27
|
dependencies = [
|
|
26
28
|
"overrides",
|
|
@@ -70,6 +72,22 @@ extra-dependencies = [
|
|
|
70
72
|
[tool.hatch.envs.types.scripts]
|
|
71
73
|
check = "mypy --install-types --non-interactive {args:src/Tables tests}"
|
|
72
74
|
|
|
75
|
+
[tool.hatch.envs.test]
|
|
76
|
+
dependencies = [
|
|
77
|
+
"pytest",
|
|
78
|
+
"pytest-cov",
|
|
79
|
+
"robotframework=={matrix:robotframework}",
|
|
80
|
+
]
|
|
81
|
+
|
|
82
|
+
[tool.hatch.envs.test.scripts]
|
|
83
|
+
atest = "robot tests/atest/"
|
|
84
|
+
utest = "pytest --cov=Tables --cov-report=term-missing --cov-branch --cov-fail-under=95"
|
|
85
|
+
all = ["utest", "atest"]
|
|
86
|
+
|
|
87
|
+
[[tool.hatch.envs.test.matrix]]
|
|
88
|
+
python = ["3.10", "3.11", "3.12", "3.13", "3.14"]
|
|
89
|
+
robotframework = ["7.0", "7.4.2"]
|
|
90
|
+
|
|
73
91
|
[tool.coverage.run]
|
|
74
92
|
source_pkgs = ["Tables", "tests"]
|
|
75
93
|
branch = true
|
|
@@ -103,6 +121,8 @@ docs = "libdoc src/Tables results/keyword_docs.html"
|
|
|
103
121
|
|
|
104
122
|
[tool.mypy]
|
|
105
123
|
python_version = "3.10"
|
|
124
|
+
exclude = '(^|/)\.venv(/|$)'
|
|
125
|
+
follow_imports = "skip"
|
|
106
126
|
ignore_missing_imports = true
|
|
107
127
|
no_implicit_optional = true
|
|
108
128
|
strict_optional = true
|
{robotframework_tablelibrary-0.2.0 → robotframework_tablelibrary-0.3.0}/src/Tables/__init__.py
RENAMED
|
@@ -172,6 +172,7 @@ class Tables(HybridCore):
|
|
|
172
172
|
line_terminator: LineTerminator = LineTerminator.LF,
|
|
173
173
|
quoting: Quoting = Quoting.MINIMAL,
|
|
174
174
|
quoting_character: QuotingCharacter = QuotingCharacter['"'],
|
|
175
|
+
missing_as_none: bool = False,
|
|
175
176
|
):
|
|
176
177
|
"""
|
|
177
178
|
``TableLibrary`` can be controlled by the following arguments:
|
|
@@ -184,6 +185,7 @@ class Tables(HybridCore):
|
|
|
184
185
|
| ``line_terminator`` | Define the required line terminator for your table files. Default is ``False`` |
|
|
185
186
|
| ``quoting`` | Define which values should be surrounded with quotes, please check the CSV quoting for more details. Default is ``MINIMAL`` |
|
|
186
187
|
| ``quoting_character`` | Define quoting character to use for writing table files. Default is ``\"`` |
|
|
188
|
+
| ``missing_as_none`` | Return parsed missing values as Python ``None`` instead of pandas missing values. Default is ``False`` |
|
|
187
189
|
"""
|
|
188
190
|
|
|
189
191
|
# required variables for SettingsScope mechanism
|
|
@@ -202,6 +204,7 @@ class Tables(HybridCore):
|
|
|
202
204
|
)
|
|
203
205
|
self.scope_stack["quoting"] = SettingsStack(quoting, self)
|
|
204
206
|
self.scope_stack["quoting_character"] = SettingsStack(quoting_character, self)
|
|
207
|
+
self.scope_stack["missing_as_none"] = SettingsStack(missing_as_none, self)
|
|
205
208
|
self.scope_stack["enable_streaming"] = SettingsStack(False, self)
|
|
206
209
|
|
|
207
210
|
self.file_access = FileAccess(self)
|
|
@@ -222,12 +225,14 @@ class Tables(HybridCore):
|
|
|
222
225
|
super().__init__(libraries)
|
|
223
226
|
|
|
224
227
|
def _start_suite(self, _name: running.TestSuite, attrs: result.TestSuite):
|
|
225
|
-
|
|
226
|
-
self.
|
|
228
|
+
suite_id = attrs.id
|
|
229
|
+
self.suite_ids[suite_id] = None
|
|
230
|
+
self._add_to_scope_stack(suite_id, Scope.Suite)
|
|
227
231
|
|
|
228
232
|
def _start_test(self, _name: running.TestCase, attrs: result.TestCase):
|
|
229
|
-
|
|
230
|
-
self.
|
|
233
|
+
test_id = attrs.id
|
|
234
|
+
self.current_test_id = test_id
|
|
235
|
+
self._add_to_scope_stack(test_id, Scope.Test)
|
|
231
236
|
self.is_test_case_running = True
|
|
232
237
|
|
|
233
238
|
def _end_test(self, _name: running.TestCase, attrs: result.TestCase):
|
|
@@ -236,8 +241,9 @@ class Tables(HybridCore):
|
|
|
236
241
|
self.is_test_case_running = False
|
|
237
242
|
|
|
238
243
|
def _end_suite(self, _name: running.TestSuite, attrs: result.TestSuite):
|
|
239
|
-
|
|
240
|
-
self.
|
|
244
|
+
suite_id = attrs.id
|
|
245
|
+
self._remove_from_scope_stack(suite_id)
|
|
246
|
+
self.suite_ids.pop(suite_id, None)
|
|
241
247
|
|
|
242
248
|
def _add_to_scope_stack(self, scope_id: str, scope: Scope):
|
|
243
249
|
for stack in self.scope_stack.values():
|
|
@@ -121,3 +121,16 @@ class LibraryAttributes:
|
|
|
121
121
|
@enable_streaming_stack.setter
|
|
122
122
|
def enable_streaming_stack(self, stack: SettingsStack):
|
|
123
123
|
self.library.scope_stack["enable_streaming"] = stack
|
|
124
|
+
|
|
125
|
+
# missing values
|
|
126
|
+
@property
|
|
127
|
+
def missing_as_none(self) -> bool:
|
|
128
|
+
return self.library.scope_stack["missing_as_none"].get()
|
|
129
|
+
|
|
130
|
+
@property
|
|
131
|
+
def missing_as_none_stack(self) -> SettingsStack:
|
|
132
|
+
return self.library.scope_stack["missing_as_none"]
|
|
133
|
+
|
|
134
|
+
@missing_as_none_stack.setter
|
|
135
|
+
def missing_as_none_stack(self, stack: SettingsStack):
|
|
136
|
+
self.library.scope_stack["missing_as_none"] = stack
|
|
@@ -25,6 +25,7 @@ class ConfigCtx:
|
|
|
25
25
|
file_encoding: FileEncoding
|
|
26
26
|
ignore_header: bool
|
|
27
27
|
enable_streaming: bool
|
|
28
|
+
missing_as_none: bool
|
|
28
29
|
|
|
29
30
|
|
|
30
31
|
class Configuration(LibraryAttributes):
|
|
@@ -45,6 +46,7 @@ class Configuration(LibraryAttributes):
|
|
|
45
46
|
file_encoding=self.file_encoding,
|
|
46
47
|
ignore_header=self.ignore_header,
|
|
47
48
|
enable_streaming=self.enable_streaming,
|
|
49
|
+
missing_as_none=self.missing_as_none,
|
|
48
50
|
)
|
|
49
51
|
)
|
|
50
52
|
|
|
@@ -199,3 +201,26 @@ class Configuration(LibraryAttributes):
|
|
|
199
201
|
self.enable_streaming_stack.set(enable_streaming, scope)
|
|
200
202
|
logger.debug(f"'file_type': old value: {old_config} - new value: {enable_streaming}")
|
|
201
203
|
return old_config
|
|
204
|
+
|
|
205
|
+
@keyword(tags=["Configuration"])
|
|
206
|
+
def configure_missing_as_none(self, missing_as_none: bool, scope: Scope = Scope.Suite):
|
|
207
|
+
"""
|
|
208
|
+
Configure whether parsed missing values are returned as Python ``None``.
|
|
209
|
+
|
|
210
|
+
When enabled, pandas missing values such as ``NaN``, ``pd.NA`` and ``NaT``
|
|
211
|
+
are converted to ``None`` in list, dictionary, cell, row and column results.
|
|
212
|
+
Values recognized as missing by the active parser, such as ``N/A`` and ``NULL``,
|
|
213
|
+
are included. DataFrame results and the library's internal DataFrames are not changed.
|
|
214
|
+
|
|
215
|
+
| =`Arguments`= | =`Description`= |
|
|
216
|
+
| ``missing_as_none`` | Set to ``True`` to return missing values as Python ``None``. Default: ``False`` |
|
|
217
|
+
| ``scope`` | Scope in which the setting applies. |
|
|
218
|
+
|
|
219
|
+
== Example ==
|
|
220
|
+
| Configure Missing As None True
|
|
221
|
+
| Configure Missing As None False
|
|
222
|
+
"""
|
|
223
|
+
old_config = self.missing_as_none
|
|
224
|
+
self.missing_as_none_stack.set(missing_as_none, scope)
|
|
225
|
+
logger.debug(f"'missing_as_none': old value: {old_config} - new value: {missing_as_none}")
|
|
226
|
+
return old_config
|
|
@@ -58,6 +58,8 @@ class Getter(LibraryAttributes):
|
|
|
58
58
|
table_df = table_df.iloc[1:]
|
|
59
59
|
data = data[1:]
|
|
60
60
|
|
|
61
|
+
data = self.file_reader.convert_missing_values(data)
|
|
62
|
+
|
|
61
63
|
if return_type == TableFormat["List of dicts"]:
|
|
62
64
|
df_for_dicts = table_df
|
|
63
65
|
|
|
@@ -65,7 +67,9 @@ class Getter(LibraryAttributes):
|
|
|
65
67
|
header = [str(x) for x in df_for_dicts.iloc[0].tolist()]
|
|
66
68
|
df_for_dicts = df_for_dicts.iloc[1:].copy()
|
|
67
69
|
df_for_dicts.columns = header
|
|
68
|
-
return
|
|
70
|
+
return self.file_reader.convert_missing_values(
|
|
71
|
+
cast(list[dict[str, Any]], df_for_dicts.to_dict(orient="records"))
|
|
72
|
+
)
|
|
69
73
|
return data
|
|
70
74
|
|
|
71
75
|
@keyword(tags=["Getter"])
|
|
@@ -196,6 +200,9 @@ class Getter(LibraryAttributes):
|
|
|
196
200
|
| =`Arguments`= | =`Description`= |
|
|
197
201
|
| ``return_type`` | Choose what type of table format it should return. Default: list of lists|
|
|
198
202
|
|
|
203
|
+
If ``Configure Missing As None`` is enabled, missing values are returned as
|
|
204
|
+
Python ``None`` for list and dictionary results. DataFrame results are unchanged.
|
|
205
|
+
|
|
199
206
|
== Return Value ==
|
|
200
207
|
Return table in form as either list of lists, list of dicts or dataframe.
|
|
201
208
|
|
|
@@ -229,6 +236,9 @@ class Getter(LibraryAttributes):
|
|
|
229
236
|
| ``assertion_expected`` | See ``robotframework-assertion-engine`` for more details |
|
|
230
237
|
| ``message`` | Custom error message for failed assertion |
|
|
231
238
|
|
|
239
|
+
If ``Configure Missing As None`` is enabled, a missing cell is returned as
|
|
240
|
+
Python ``None`` and assertions are evaluated against the converted value.
|
|
241
|
+
|
|
232
242
|
== Return Value ==
|
|
233
243
|
Keyword returns the value of the given cell.
|
|
234
244
|
In case of a failed assertion, the keyword will just fail without returning anything.
|
|
@@ -245,6 +255,7 @@ class Getter(LibraryAttributes):
|
|
|
245
255
|
column = self.file_reader.cast_column_type(column)
|
|
246
256
|
|
|
247
257
|
cell = table_df.loc[row, column] if isinstance(column, str) else table_df.iloc[row, column]
|
|
258
|
+
cell = self.file_reader.convert_missing_values(cell)
|
|
248
259
|
|
|
249
260
|
if assertion_expected:
|
|
250
261
|
if assertion_operator not in NumericalOperators:
|
|
@@ -272,6 +283,9 @@ class Getter(LibraryAttributes):
|
|
|
272
283
|
| ``assertion_expected`` | See ``robotframework-assertion-engine`` for more details |
|
|
273
284
|
| ``message`` | Custom error message for failed assertion |
|
|
274
285
|
|
|
286
|
+
If ``Configure Missing As None`` is enabled, missing column values are returned
|
|
287
|
+
as Python ``None`` and assertions are evaluated against the converted values.
|
|
288
|
+
|
|
275
289
|
== Return Value ==
|
|
276
290
|
Returns column values as a list.
|
|
277
291
|
|
|
@@ -291,7 +305,7 @@ class Getter(LibraryAttributes):
|
|
|
291
305
|
column = self.file_reader.cast_column_type(column)
|
|
292
306
|
|
|
293
307
|
column_df = table_df.loc[:, column] if isinstance(column, str) else table_df.iloc[:, column]
|
|
294
|
-
column_list = cast(list[Any], column_df.to_list())
|
|
308
|
+
column_list = self.file_reader.convert_missing_values(cast(list[Any], column_df.to_list()))
|
|
295
309
|
|
|
296
310
|
if assertion_expected:
|
|
297
311
|
if assertion_operator not in valid_assertions:
|
|
@@ -318,6 +332,9 @@ class Getter(LibraryAttributes):
|
|
|
318
332
|
| ``assertion_expected`` | See ``robotframework-assertion-engine`` for more details |
|
|
319
333
|
| ``message`` | Custom error message for failed assertion |
|
|
320
334
|
|
|
335
|
+
If ``Configure Missing As None`` is enabled, missing row values are returned as
|
|
336
|
+
Python ``None`` and assertions are evaluated against the converted values.
|
|
337
|
+
|
|
321
338
|
== Return Value ==
|
|
322
339
|
Returns row values as a list.
|
|
323
340
|
|
|
@@ -335,7 +352,7 @@ class Getter(LibraryAttributes):
|
|
|
335
352
|
current_df = self.file_reader.file_sync.table_storage[self.file_reader.file_sync.current_file].data
|
|
336
353
|
table_df = self.file_reader.validate_table_to_dataframe(data=current_df, row=row)
|
|
337
354
|
|
|
338
|
-
row_list = cast(list[Any], table_df.iloc[row].to_list())
|
|
355
|
+
row_list = self.file_reader.convert_missing_values(cast(list[Any], table_df.iloc[row].to_list()))
|
|
339
356
|
|
|
340
357
|
if assertion_expected:
|
|
341
358
|
if assertion_operator not in valid_assertions:
|
|
@@ -347,7 +364,7 @@ class Getter(LibraryAttributes):
|
|
|
347
364
|
return row_list
|
|
348
365
|
|
|
349
366
|
@keyword(tags=["Getter"])
|
|
350
|
-
def count_table( # noqa
|
|
367
|
+
def count_table( # noqa PLR0913
|
|
351
368
|
self,
|
|
352
369
|
path: Path | str,
|
|
353
370
|
axis: Axis,
|
|
@@ -50,7 +50,7 @@ class FileReader(LibraryAttributes):
|
|
|
50
50
|
if self.file_type == FileType.Parquet and not self.ignore_header:
|
|
51
51
|
list_data.insert(0, list(data.columns))
|
|
52
52
|
|
|
53
|
-
return list_data
|
|
53
|
+
return self.convert_missing_values(list_data)
|
|
54
54
|
if return_type == TableFormat["List of dicts"]:
|
|
55
55
|
df_for_dicts = data
|
|
56
56
|
|
|
@@ -58,11 +58,25 @@ class FileReader(LibraryAttributes):
|
|
|
58
58
|
header = [str(x) for x in df_for_dicts.iloc[0].tolist()]
|
|
59
59
|
df_for_dicts = df_for_dicts.iloc[1:].copy()
|
|
60
60
|
df_for_dicts.columns = header
|
|
61
|
-
return cast(list[dict[str, Any]], df_for_dicts.to_dict(orient="records"))
|
|
61
|
+
return self.convert_missing_values(cast(list[dict[str, Any]], df_for_dicts.to_dict(orient="records")))
|
|
62
62
|
if return_type == TableFormat["Dataframe"]:
|
|
63
63
|
return data
|
|
64
64
|
raise ValueError(f"Invalid TableFormat type. Please select valid values: {[key.name for key in TableFormat]}")
|
|
65
65
|
|
|
66
|
+
def convert_missing_values(self, value: Any) -> Any:
|
|
67
|
+
if not self.missing_as_none:
|
|
68
|
+
return value
|
|
69
|
+
if isinstance(value, list):
|
|
70
|
+
return [self.convert_missing_values(item) for item in value]
|
|
71
|
+
if isinstance(value, dict):
|
|
72
|
+
return {key: self.convert_missing_values(item) for key, item in value.items()}
|
|
73
|
+
try:
|
|
74
|
+
if bool(pd.isna(value)):
|
|
75
|
+
return None
|
|
76
|
+
except (TypeError, ValueError):
|
|
77
|
+
pass
|
|
78
|
+
return value
|
|
79
|
+
|
|
66
80
|
def file_exists(self, path: Path) -> bool | FileNotFoundError:
|
|
67
81
|
if not path.is_file():
|
|
68
82
|
raise FileNotFoundError(f"File not found: {path}")
|
|
@@ -206,8 +220,8 @@ class FileReader(LibraryAttributes):
|
|
|
206
220
|
def validate_table_to_dataframe(
|
|
207
221
|
self,
|
|
208
222
|
data: list[list] | DataFrame,
|
|
209
|
-
row:
|
|
210
|
-
column:
|
|
223
|
+
row: int | None = None,
|
|
224
|
+
column: str | int | None = None,
|
|
211
225
|
) -> DataFrame:
|
|
212
226
|
"""Formats a table (list of lists or dataframe) to dataframe. Also checks
|
|
213
227
|
if provided row or column are valid (see validate_row/ validate_column)."""
|
|
@@ -260,7 +274,7 @@ class FileReader(LibraryAttributes):
|
|
|
260
274
|
FileType.Parquet: lambda: self.read_parquet(path),
|
|
261
275
|
}
|
|
262
276
|
|
|
263
|
-
self.config.configure_file_type(self.read_data_type(path), Scope.
|
|
277
|
+
self.config.configure_file_type(self.read_data_type(path), Scope.Suite)
|
|
264
278
|
|
|
265
279
|
reader = readers.get(self.file_type)
|
|
266
280
|
if reader:
|
|
@@ -183,7 +183,7 @@ class FileWriter(LibraryAttributes):
|
|
|
183
183
|
else:
|
|
184
184
|
raise TypeError("Invalid file_path type.")
|
|
185
185
|
|
|
186
|
-
self.config.configure_file_type(self.file_reader.read_data_type(file_path), Scope.
|
|
186
|
+
self.config.configure_file_type(self.file_reader.read_data_type(file_path), Scope.Suite)
|
|
187
187
|
|
|
188
188
|
if isinstance(data, list) and self.file_type == FileType.Parquet:
|
|
189
189
|
data_df = pd.DataFrame(data[1:], columns=data[0])
|
|
@@ -219,8 +219,8 @@ class FileWriter(LibraryAttributes):
|
|
|
219
219
|
def set_dataframe_cells(
|
|
220
220
|
self,
|
|
221
221
|
data: Any,
|
|
222
|
-
row:
|
|
223
|
-
column:
|
|
222
|
+
row: int | None = None,
|
|
223
|
+
column: str | int | None = None,
|
|
224
224
|
header: bool = True,
|
|
225
225
|
return_type: TableFormat = TableFormat["Dataframe"],
|
|
226
226
|
) -> list[list] | list[dict] | DataFrame:
|
|
@@ -231,7 +231,7 @@ class FileWriter(LibraryAttributes):
|
|
|
231
231
|
|
|
232
232
|
# overwrite ignore_header for validation keywords
|
|
233
233
|
self.header = header
|
|
234
|
-
self.config.configure_ignore_header(not self.header, Scope.
|
|
234
|
+
self.config.configure_ignore_header(not self.header, Scope.Suite)
|
|
235
235
|
|
|
236
236
|
table_df = self.add_header_in_dataframe(table_df)
|
|
237
237
|
|
|
@@ -251,14 +251,14 @@ class FileWriter(LibraryAttributes):
|
|
|
251
251
|
if row:
|
|
252
252
|
self.file_reader.validate_row(table_df, axis_row)
|
|
253
253
|
|
|
254
|
-
if isinstance(axis_column, str):
|
|
254
|
+
if isinstance(axis_column, str) or len(table_df) == 0:
|
|
255
255
|
table_df.loc[axis_row, axis_column] = data
|
|
256
256
|
else:
|
|
257
257
|
table_df.iloc[axis_row, axis_column] = data
|
|
258
258
|
|
|
259
259
|
table_df = self.update_cached_dataframe(table_df)
|
|
260
260
|
|
|
261
|
-
self.config.configure_ignore_header(original_ignore_header, Scope.
|
|
261
|
+
self.config.configure_ignore_header(original_ignore_header, Scope.Suite)
|
|
262
262
|
self.header = original_header
|
|
263
263
|
|
|
264
264
|
return self.file_reader.convert_dataframe(table_df, return_type)
|
|
@@ -284,7 +284,7 @@ class FileWriter(LibraryAttributes):
|
|
|
284
284
|
else:
|
|
285
285
|
self.header = header
|
|
286
286
|
|
|
287
|
-
self.config.configure_ignore_header(not self.header, Scope.
|
|
287
|
+
self.config.configure_ignore_header(not self.header, Scope.Suite)
|
|
288
288
|
|
|
289
289
|
table_df = self.add_header_in_dataframe(table_df)
|
|
290
290
|
|
|
@@ -311,7 +311,7 @@ class FileWriter(LibraryAttributes):
|
|
|
311
311
|
|
|
312
312
|
self.update_cached_dataframe(table_df)
|
|
313
313
|
|
|
314
|
-
self.config.configure_ignore_header(original_ignore_header, Scope.
|
|
314
|
+
self.config.configure_ignore_header(original_ignore_header, Scope.Suite)
|
|
315
315
|
self.header = original_header
|
|
316
316
|
|
|
317
317
|
return table_df
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
*** Settings ***
|
|
2
|
+
Library Tables
|
|
3
|
+
Suite Setup Suite Setup & Teardown - Verify Scope Settings
|
|
4
|
+
Suite Teardown Suite Setup & Teardown - Verify Scope Settings
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
*** Keywords ***
|
|
8
|
+
Suite Setup & Teardown - Verify Scope Settings
|
|
9
|
+
VAR @{header} = name age
|
|
10
|
+
Tables.Create Table ${header}
|
|
11
|
+
Tables.Insert Row ${{["peter", "55"]}} -1
|
|
12
|
+
Tables.Insert Row ${{["marvin", "26"]}} -1
|
|
13
|
+
${table} = Tables.Get Table
|
|
14
|
+
Should Be Equal ${table}[0][0] name
|
|
15
|
+
Should Be Equal ${table}[1][0] peter
|
|
16
|
+
Should Be Equal ${table}[2][0] marvin
|
|
17
|
+
Log 1234
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
*** Settings ***
|
|
2
|
+
Library Tables missing_as_none=True
|
|
3
|
+
Library Collections
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
*** Test Cases ***
|
|
7
|
+
Missing Values - Library Argument Returns None
|
|
8
|
+
${data} = Tables.Read Table ${CURDIR}${/}testdata${/}missing_values.csv
|
|
9
|
+
@{header} = Create List name email empty
|
|
10
|
+
Lists Should Be Equal ${data}[0] ${header}
|
|
11
|
+
Should Be Equal ${data}[1][2] ${None}
|
|
12
|
+
Should Be Equal ${data}[2][1] ${None}
|
|
13
|
+
|
|
14
|
+
Tables.Open Table ${CURDIR}${/}testdata${/}missing_values.csv missing
|
|
15
|
+
${cell} = Tables.Get Table Cell 1 email
|
|
16
|
+
Should Be Equal ${cell} ${None}
|
|
17
|
+
${column} = Tables.Get Table Column empty
|
|
18
|
+
@{empty_column} = Create List ${None} ${None}
|
|
19
|
+
Lists Should Be Equal ${column} ${empty_column}
|
|
20
|
+
${row} = Tables.Get Table Row 2
|
|
21
|
+
@{expected_row} = Create List Bob ${None} ${None}
|
|
22
|
+
Lists Should Be Equal ${row} ${expected_row}
|
|
23
|
+
|
|
24
|
+
Missing Values - Configuration Keyword Toggles Conversion
|
|
25
|
+
Tables.Configure Missing As None False
|
|
26
|
+
${data} = Tables.Read Table ${CURDIR}${/}testdata${/}missing_values.csv
|
|
27
|
+
Should Not Be Equal ${data}[1][2] ${None}
|
|
28
|
+
|
|
29
|
+
Tables.Configure Missing As None True
|
|
30
|
+
${data} = Tables.Read Table ${CURDIR}${/}testdata${/}missing_values.csv
|
|
31
|
+
Should Be Equal ${data}[1][2] ${None}
|
|
@@ -1,8 +1,15 @@
|
|
|
1
1
|
*** Settings ***
|
|
2
2
|
Library Tables ignore_header=True
|
|
3
|
+
Suite Setup Suite Setup & Teardown - Verify Scope Settings
|
|
4
|
+
Suite Teardown Suite Setup & Teardown - Verify Scope Settings
|
|
3
5
|
|
|
4
6
|
|
|
5
7
|
*** Test Cases ***
|
|
8
|
+
TC-000 - Verify Test Setup & Teardown
|
|
9
|
+
[Setup] Suite Setup & Teardown - Verify Scope Settings
|
|
10
|
+
[Teardown] Suite Setup & Teardown - Verify Scope Settings
|
|
11
|
+
Log Verify Test Setup and Teardown
|
|
12
|
+
|
|
6
13
|
TC-001 - Suite Scope - Change Parameter with Scope - Ignore Header - False
|
|
7
14
|
[Documentation] Set config parameter with suite scope
|
|
8
15
|
[Setup] Configure Ignore Header ${True}
|
|
@@ -34,3 +41,20 @@ TC-002 - Test Scope - Parameter Should Be - Ignore Header - True
|
|
|
34
41
|
&{cfg} = Tables.Get Library Configuration
|
|
35
42
|
Should Be Equal ${cfg.ignore_header} ${True}
|
|
36
43
|
|
|
44
|
+
|
|
45
|
+
*** Keywords ***
|
|
46
|
+
Suite Setup & Teardown - Verify Scope Settings
|
|
47
|
+
VAR @{header} = name age
|
|
48
|
+
Configure Ignore Header False
|
|
49
|
+
Tables.Create Table ${header}
|
|
50
|
+
Tables.Append Row ${{["peter", "55"]}}
|
|
51
|
+
Tables.Set Table Row ${{["benni", "30"]}} 0
|
|
52
|
+
Tables.Insert Row ${{["peter", "55"]}} -1
|
|
53
|
+
Tables.Insert Row ${{["marvin", "26"]}} -1
|
|
54
|
+
${table} = Tables.Get Table
|
|
55
|
+
|
|
56
|
+
Should Be Equal ${table}[0][0] name
|
|
57
|
+
Should Be Equal ${table}[1][0] benni
|
|
58
|
+
Should Be Equal ${table}[2][0] peter
|
|
59
|
+
Should Be Equal ${table}[3][0] marvin
|
|
60
|
+
Tables.Write Table ${table} ${CURDIR}${/}results${/}setupteardown.csv
|
|
@@ -23,7 +23,7 @@ from Tables.utils.settings_stack import SettingsStack
|
|
|
23
23
|
|
|
24
24
|
|
|
25
25
|
class DummyLibrary:
|
|
26
|
-
def __init__(self, *, ignore_header: bool = False):
|
|
26
|
+
def __init__(self, *, ignore_header: bool = False, missing_as_none: bool = False):
|
|
27
27
|
|
|
28
28
|
# attributes expected by SettingsStack
|
|
29
29
|
self.scope_stack: dict = {}
|
|
@@ -32,9 +32,11 @@ class DummyLibrary:
|
|
|
32
32
|
self.is_test_case_running: bool = False
|
|
33
33
|
|
|
34
34
|
self.scope_stack["ignore_header"] = SettingsStack(ignore_header, self)
|
|
35
|
+
self.scope_stack["missing_as_none"] = SettingsStack(missing_as_none, self)
|
|
35
36
|
self.scope_stack["file_type"] = SettingsStack(FileType.CSV, self)
|
|
36
37
|
self.scope_stack["separator"] = SettingsStack(Delimiter[","], self)
|
|
37
38
|
self.scope_stack["line_terminator"] = SettingsStack(LineTerminator.LF, self)
|
|
38
39
|
self.scope_stack["file_encoding"] = SettingsStack(FileEncoding.UTF_8.value, self)
|
|
39
40
|
self.scope_stack["quoting"] = SettingsStack(Quoting.MINIMAL, self)
|
|
40
41
|
self.scope_stack["quoting_character"] = SettingsStack(QuotingCharacter['"'], self)
|
|
42
|
+
self.scope_stack["enable_streaming"] = SettingsStack(False, self)
|