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.
Files changed (70) hide show
  1. {robotframework_tablelibrary-0.2.0 → robotframework_tablelibrary-0.3.0}/.github/workflows/build.yml +11 -52
  2. {robotframework_tablelibrary-0.2.0 → robotframework_tablelibrary-0.3.0}/DEVELOPMENT.md +18 -0
  3. {robotframework_tablelibrary-0.2.0 → robotframework_tablelibrary-0.3.0}/PKG-INFO +15 -2
  4. {robotframework_tablelibrary-0.2.0 → robotframework_tablelibrary-0.3.0}/README.md +11 -0
  5. {robotframework_tablelibrary-0.2.0 → robotframework_tablelibrary-0.3.0}/pyproject.toml +20 -0
  6. {robotframework_tablelibrary-0.2.0 → robotframework_tablelibrary-0.3.0}/src/Tables/__about__.py +1 -1
  7. {robotframework_tablelibrary-0.2.0 → robotframework_tablelibrary-0.3.0}/src/Tables/__init__.py +12 -6
  8. {robotframework_tablelibrary-0.2.0 → robotframework_tablelibrary-0.3.0}/src/Tables/general/library_attributes.py +13 -0
  9. {robotframework_tablelibrary-0.2.0 → robotframework_tablelibrary-0.3.0}/src/Tables/keywords/configuration.py +25 -0
  10. {robotframework_tablelibrary-0.2.0 → robotframework_tablelibrary-0.3.0}/src/Tables/keywords/getter.py +21 -4
  11. {robotframework_tablelibrary-0.2.0 → robotframework_tablelibrary-0.3.0}/src/Tables/utils/file_reader.py +19 -5
  12. {robotframework_tablelibrary-0.2.0 → robotframework_tablelibrary-0.3.0}/src/Tables/utils/file_writer.py +8 -8
  13. robotframework_tablelibrary-0.3.0/tests/atest/__init__.robot +17 -0
  14. robotframework_tablelibrary-0.3.0/tests/atest/test_missing_values.robot +31 -0
  15. {robotframework_tablelibrary-0.2.0 → robotframework_tablelibrary-0.3.0}/tests/atest/test_settings_scopes.robot +24 -0
  16. robotframework_tablelibrary-0.3.0/tests/atest/testdata/missing_values.csv +3 -0
  17. {robotframework_tablelibrary-0.2.0 → robotframework_tablelibrary-0.3.0}/tests/utest/helper/helpers.py +3 -1
  18. {robotframework_tablelibrary-0.2.0 → robotframework_tablelibrary-0.3.0}/tests/utest/keywords/test_keywords_configuration.py +15 -0
  19. {robotframework_tablelibrary-0.2.0 → robotframework_tablelibrary-0.3.0}/tests/utest/keywords/test_keywords_getter.py +82 -0
  20. {robotframework_tablelibrary-0.2.0 → robotframework_tablelibrary-0.3.0}/tests/utest/test_tables_init.py +3 -0
  21. robotframework_tablelibrary-0.3.0/uv.lock +2267 -0
  22. {robotframework_tablelibrary-0.2.0 → robotframework_tablelibrary-0.3.0}/.github/workflows/release.yml +0 -0
  23. {robotframework_tablelibrary-0.2.0 → robotframework_tablelibrary-0.3.0}/.gitignore +0 -0
  24. {robotframework_tablelibrary-0.2.0 → robotframework_tablelibrary-0.3.0}/.pre-commit-config.yaml +0 -0
  25. {robotframework_tablelibrary-0.2.0 → robotframework_tablelibrary-0.3.0}/LICENSE +0 -0
  26. {robotframework_tablelibrary-0.2.0 → robotframework_tablelibrary-0.3.0}/create_release.sh +0 -0
  27. {robotframework_tablelibrary-0.2.0 → robotframework_tablelibrary-0.3.0}/src/Tables/general/__init__.py +0 -0
  28. {robotframework_tablelibrary-0.2.0 → robotframework_tablelibrary-0.3.0}/src/Tables/keywords/__init__.py +0 -0
  29. {robotframework_tablelibrary-0.2.0 → robotframework_tablelibrary-0.3.0}/src/Tables/keywords/excel.py +0 -0
  30. {robotframework_tablelibrary-0.2.0 → robotframework_tablelibrary-0.3.0}/src/Tables/keywords/modifier.py +0 -0
  31. {robotframework_tablelibrary-0.2.0 → robotframework_tablelibrary-0.3.0}/src/Tables/keywords/writer.py +0 -0
  32. {robotframework_tablelibrary-0.2.0 → robotframework_tablelibrary-0.3.0}/src/Tables/utils/__init__.py +0 -0
  33. {robotframework_tablelibrary-0.2.0 → robotframework_tablelibrary-0.3.0}/src/Tables/utils/file_access.py +0 -0
  34. {robotframework_tablelibrary-0.2.0 → robotframework_tablelibrary-0.3.0}/src/Tables/utils/file_system.py +0 -0
  35. {robotframework_tablelibrary-0.2.0 → robotframework_tablelibrary-0.3.0}/src/Tables/utils/settings.py +0 -0
  36. {robotframework_tablelibrary-0.2.0 → robotframework_tablelibrary-0.3.0}/src/Tables/utils/settings_stack.py +0 -0
  37. {robotframework_tablelibrary-0.2.0 → robotframework_tablelibrary-0.3.0}/tests/__init__.py +0 -0
  38. {robotframework_tablelibrary-0.2.0 → robotframework_tablelibrary-0.3.0}/tests/atest/test_excel.robot +0 -0
  39. {robotframework_tablelibrary-0.2.0 → robotframework_tablelibrary-0.3.0}/tests/atest/test_reader.robot +0 -0
  40. {robotframework_tablelibrary-0.2.0 → robotframework_tablelibrary-0.3.0}/tests/atest/test_settings.robot +0 -0
  41. {robotframework_tablelibrary-0.2.0 → robotframework_tablelibrary-0.3.0}/tests/atest/test_writer.robot +0 -0
  42. {robotframework_tablelibrary-0.2.0 → robotframework_tablelibrary-0.3.0}/tests/atest/testdata/example_01.csv +0 -0
  43. {robotframework_tablelibrary-0.2.0 → robotframework_tablelibrary-0.3.0}/tests/atest/testdata/example_01.txt +0 -0
  44. {robotframework_tablelibrary-0.2.0 → robotframework_tablelibrary-0.3.0}/tests/atest/testdata/example_02.xlsx +0 -0
  45. {robotframework_tablelibrary-0.2.0 → robotframework_tablelibrary-0.3.0}/tests/atest/testdata/example_03.parquet +0 -0
  46. {robotframework_tablelibrary-0.2.0 → robotframework_tablelibrary-0.3.0}/tests/atest/testdata/example_04.csv +0 -0
  47. {robotframework_tablelibrary-0.2.0 → robotframework_tablelibrary-0.3.0}/tests/atest/testdata/example_04.txt +0 -0
  48. {robotframework_tablelibrary-0.2.0 → robotframework_tablelibrary-0.3.0}/tests/atest/testdata/example_05.parquet +0 -0
  49. {robotframework_tablelibrary-0.2.0 → robotframework_tablelibrary-0.3.0}/tests/atest/testdata/example_06.xlsx +0 -0
  50. {robotframework_tablelibrary-0.2.0 → robotframework_tablelibrary-0.3.0}/tests/atest/testdata/example_07.csv +0 -0
  51. {robotframework_tablelibrary-0.2.0 → robotframework_tablelibrary-0.3.0}/tests/atest/testdata/example_08.csv +0 -0
  52. {robotframework_tablelibrary-0.2.0 → robotframework_tablelibrary-0.3.0}/tests/atest/testdata/example_09_streaming.csv +0 -0
  53. {robotframework_tablelibrary-0.2.0 → robotframework_tablelibrary-0.3.0}/tests/utest/__init__.py +0 -0
  54. {robotframework_tablelibrary-0.2.0 → robotframework_tablelibrary-0.3.0}/tests/utest/helper/__init__.py +0 -0
  55. {robotframework_tablelibrary-0.2.0 → robotframework_tablelibrary-0.3.0}/tests/utest/keywords/__init__.py +0 -0
  56. {robotframework_tablelibrary-0.2.0 → robotframework_tablelibrary-0.3.0}/tests/utest/keywords/conftest.py +0 -0
  57. {robotframework_tablelibrary-0.2.0 → robotframework_tablelibrary-0.3.0}/tests/utest/keywords/test_keywords_excel.py +0 -0
  58. {robotframework_tablelibrary-0.2.0 → robotframework_tablelibrary-0.3.0}/tests/utest/keywords/test_keywords_init.py +0 -0
  59. {robotframework_tablelibrary-0.2.0 → robotframework_tablelibrary-0.3.0}/tests/utest/keywords/test_keywords_modifier.py +0 -0
  60. {robotframework_tablelibrary-0.2.0 → robotframework_tablelibrary-0.3.0}/tests/utest/keywords/test_keywords_writer.py +0 -0
  61. {robotframework_tablelibrary-0.2.0 → robotframework_tablelibrary-0.3.0}/tests/utest/test_configuration.py +0 -0
  62. {robotframework_tablelibrary-0.2.0 → robotframework_tablelibrary-0.3.0}/tests/utest/test_getter.py +0 -0
  63. {robotframework_tablelibrary-0.2.0 → robotframework_tablelibrary-0.3.0}/tests/utest/test_utils_file_access.py +0 -0
  64. {robotframework_tablelibrary-0.2.0 → robotframework_tablelibrary-0.3.0}/tests/utest/test_utils_file_reader.py +0 -0
  65. {robotframework_tablelibrary-0.2.0 → robotframework_tablelibrary-0.3.0}/tests/utest/test_utils_file_system.py +0 -0
  66. {robotframework_tablelibrary-0.2.0 → robotframework_tablelibrary-0.3.0}/tests/utest/test_utils_file_writer.py +0 -0
  67. {robotframework_tablelibrary-0.2.0 → robotframework_tablelibrary-0.3.0}/tests/utest/test_utils_init.py +0 -0
  68. {robotframework_tablelibrary-0.2.0 → robotframework_tablelibrary-0.3.0}/tests/utest/test_utils_settings.py +0 -0
  69. {robotframework_tablelibrary-0.2.0 → robotframework_tablelibrary-0.3.0}/tests/utest/test_utils_settings_stack.py +0 -0
  70. {robotframework_tablelibrary-0.2.0 → robotframework_tablelibrary-0.3.0}/utils/imbus-web-logo.svg +0 -0
@@ -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 dev
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
- run-acceptance-tests:
68
- runs-on: ubuntu-latest
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: Set up Python
75
- uses: actions/setup-python@v5
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 robot framework acceptance-tests
89
- run: hatch run dev:atest
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 unit tests with pytest
113
- run: hatch run dev:utest
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.4
1
+ Metadata-Version: 2.5
2
2
  Name: robotframework-tablelibrary
3
- Version: 0.2.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
@@ -1,4 +1,4 @@
1
1
  # SPDX-FileCopyrightText: 2025-present Marvin Klerx <marvinklerx20@gmail.com>
2
2
  #
3
3
  # SPDX-License-Identifier: MIT
4
- __version__ = "0.2.0"
4
+ __version__ = "0.3.0"
@@ -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
- self.suite_ids[attrs.id] = None
226
- self._add_to_scope_stack(attrs.id, Scope.Suite)
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
- self.current_test_id = attrs.id
230
- self._add_to_scope_stack(attrs.id, Scope.Test)
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
- self._remove_from_scope_stack(attrs.id)
240
- self.suite_ids.pop(attrs.id, None)
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 cast(list[dict[str, Any]], df_for_dicts.to_dict(orient="records"))
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: PLR0913
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: None | int = None,
210
- column: None | str | int = None,
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.Test)
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.Test)
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: None | int = None,
223
- column: None | str | int = None,
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.Test)
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.Test)
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.Test)
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.Test)
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
@@ -0,0 +1,3 @@
1
+ name,email,empty
2
+ Alice,alice@example.com,
3
+ Bob,,
@@ -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)