robotframework-tablelibrary 0.2.1__tar.gz → 0.3.1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (72) hide show
  1. {robotframework_tablelibrary-0.2.1 → robotframework_tablelibrary-0.3.1}/.github/workflows/build.yml +11 -52
  2. {robotframework_tablelibrary-0.2.1 → robotframework_tablelibrary-0.3.1}/.github/workflows/release.yml +4 -0
  3. {robotframework_tablelibrary-0.2.1 → robotframework_tablelibrary-0.3.1}/.gitignore +1 -0
  4. {robotframework_tablelibrary-0.2.1 → robotframework_tablelibrary-0.3.1}/DEVELOPMENT.md +31 -21
  5. {robotframework_tablelibrary-0.2.1 → robotframework_tablelibrary-0.3.1}/PKG-INFO +15 -2
  6. {robotframework_tablelibrary-0.2.1 → robotframework_tablelibrary-0.3.1}/README.md +11 -0
  7. robotframework_tablelibrary-0.3.1/create_release.sh +32 -0
  8. {robotframework_tablelibrary-0.2.1 → robotframework_tablelibrary-0.3.1}/pyproject.toml +26 -3
  9. {robotframework_tablelibrary-0.2.1 → robotframework_tablelibrary-0.3.1}/src/Tables/__init__.py +13 -7
  10. robotframework_tablelibrary-0.3.1/src/Tables/_version.py +24 -0
  11. {robotframework_tablelibrary-0.2.1 → robotframework_tablelibrary-0.3.1}/src/Tables/general/library_attributes.py +13 -0
  12. {robotframework_tablelibrary-0.2.1 → robotframework_tablelibrary-0.3.1}/src/Tables/keywords/configuration.py +25 -0
  13. {robotframework_tablelibrary-0.2.1 → robotframework_tablelibrary-0.3.1}/src/Tables/keywords/getter.py +21 -4
  14. {robotframework_tablelibrary-0.2.1 → robotframework_tablelibrary-0.3.1}/src/Tables/utils/file_reader.py +18 -4
  15. {robotframework_tablelibrary-0.2.1 → robotframework_tablelibrary-0.3.1}/src/Tables/utils/file_writer.py +2 -2
  16. robotframework_tablelibrary-0.3.1/tests/atest/test_missing_values.robot +31 -0
  17. robotframework_tablelibrary-0.3.1/tests/atest/testdata/missing_values.csv +3 -0
  18. {robotframework_tablelibrary-0.2.1 → robotframework_tablelibrary-0.3.1}/tests/utest/helper/helpers.py +3 -1
  19. {robotframework_tablelibrary-0.2.1 → robotframework_tablelibrary-0.3.1}/tests/utest/keywords/test_keywords_configuration.py +15 -0
  20. {robotframework_tablelibrary-0.2.1 → robotframework_tablelibrary-0.3.1}/tests/utest/keywords/test_keywords_getter.py +82 -0
  21. {robotframework_tablelibrary-0.2.1 → robotframework_tablelibrary-0.3.1}/tests/utest/test_tables_init.py +3 -0
  22. robotframework_tablelibrary-0.3.1/uv.lock +2267 -0
  23. robotframework_tablelibrary-0.2.1/create_release.sh +0 -32
  24. robotframework_tablelibrary-0.2.1/src/Tables/__about__.py +0 -4
  25. {robotframework_tablelibrary-0.2.1 → robotframework_tablelibrary-0.3.1}/.pre-commit-config.yaml +0 -0
  26. {robotframework_tablelibrary-0.2.1 → robotframework_tablelibrary-0.3.1}/LICENSE +0 -0
  27. {robotframework_tablelibrary-0.2.1 → robotframework_tablelibrary-0.3.1}/src/Tables/general/__init__.py +0 -0
  28. {robotframework_tablelibrary-0.2.1 → robotframework_tablelibrary-0.3.1}/src/Tables/keywords/__init__.py +0 -0
  29. {robotframework_tablelibrary-0.2.1 → robotframework_tablelibrary-0.3.1}/src/Tables/keywords/excel.py +0 -0
  30. {robotframework_tablelibrary-0.2.1 → robotframework_tablelibrary-0.3.1}/src/Tables/keywords/modifier.py +0 -0
  31. {robotframework_tablelibrary-0.2.1 → robotframework_tablelibrary-0.3.1}/src/Tables/keywords/writer.py +0 -0
  32. {robotframework_tablelibrary-0.2.1 → robotframework_tablelibrary-0.3.1}/src/Tables/utils/__init__.py +0 -0
  33. {robotframework_tablelibrary-0.2.1 → robotframework_tablelibrary-0.3.1}/src/Tables/utils/file_access.py +0 -0
  34. {robotframework_tablelibrary-0.2.1 → robotframework_tablelibrary-0.3.1}/src/Tables/utils/file_system.py +0 -0
  35. {robotframework_tablelibrary-0.2.1 → robotframework_tablelibrary-0.3.1}/src/Tables/utils/settings.py +0 -0
  36. {robotframework_tablelibrary-0.2.1 → robotframework_tablelibrary-0.3.1}/src/Tables/utils/settings_stack.py +0 -0
  37. {robotframework_tablelibrary-0.2.1 → robotframework_tablelibrary-0.3.1}/tests/__init__.py +0 -0
  38. {robotframework_tablelibrary-0.2.1 → robotframework_tablelibrary-0.3.1}/tests/atest/__init__.robot +0 -0
  39. {robotframework_tablelibrary-0.2.1 → robotframework_tablelibrary-0.3.1}/tests/atest/test_excel.robot +0 -0
  40. {robotframework_tablelibrary-0.2.1 → robotframework_tablelibrary-0.3.1}/tests/atest/test_reader.robot +0 -0
  41. {robotframework_tablelibrary-0.2.1 → robotframework_tablelibrary-0.3.1}/tests/atest/test_settings.robot +0 -0
  42. {robotframework_tablelibrary-0.2.1 → robotframework_tablelibrary-0.3.1}/tests/atest/test_settings_scopes.robot +0 -0
  43. {robotframework_tablelibrary-0.2.1 → robotframework_tablelibrary-0.3.1}/tests/atest/test_writer.robot +0 -0
  44. {robotframework_tablelibrary-0.2.1 → robotframework_tablelibrary-0.3.1}/tests/atest/testdata/example_01.csv +0 -0
  45. {robotframework_tablelibrary-0.2.1 → robotframework_tablelibrary-0.3.1}/tests/atest/testdata/example_01.txt +0 -0
  46. {robotframework_tablelibrary-0.2.1 → robotframework_tablelibrary-0.3.1}/tests/atest/testdata/example_02.xlsx +0 -0
  47. {robotframework_tablelibrary-0.2.1 → robotframework_tablelibrary-0.3.1}/tests/atest/testdata/example_03.parquet +0 -0
  48. {robotframework_tablelibrary-0.2.1 → robotframework_tablelibrary-0.3.1}/tests/atest/testdata/example_04.csv +0 -0
  49. {robotframework_tablelibrary-0.2.1 → robotframework_tablelibrary-0.3.1}/tests/atest/testdata/example_04.txt +0 -0
  50. {robotframework_tablelibrary-0.2.1 → robotframework_tablelibrary-0.3.1}/tests/atest/testdata/example_05.parquet +0 -0
  51. {robotframework_tablelibrary-0.2.1 → robotframework_tablelibrary-0.3.1}/tests/atest/testdata/example_06.xlsx +0 -0
  52. {robotframework_tablelibrary-0.2.1 → robotframework_tablelibrary-0.3.1}/tests/atest/testdata/example_07.csv +0 -0
  53. {robotframework_tablelibrary-0.2.1 → robotframework_tablelibrary-0.3.1}/tests/atest/testdata/example_08.csv +0 -0
  54. {robotframework_tablelibrary-0.2.1 → robotframework_tablelibrary-0.3.1}/tests/atest/testdata/example_09_streaming.csv +0 -0
  55. {robotframework_tablelibrary-0.2.1 → robotframework_tablelibrary-0.3.1}/tests/utest/__init__.py +0 -0
  56. {robotframework_tablelibrary-0.2.1 → robotframework_tablelibrary-0.3.1}/tests/utest/helper/__init__.py +0 -0
  57. {robotframework_tablelibrary-0.2.1 → robotframework_tablelibrary-0.3.1}/tests/utest/keywords/__init__.py +0 -0
  58. {robotframework_tablelibrary-0.2.1 → robotframework_tablelibrary-0.3.1}/tests/utest/keywords/conftest.py +0 -0
  59. {robotframework_tablelibrary-0.2.1 → robotframework_tablelibrary-0.3.1}/tests/utest/keywords/test_keywords_excel.py +0 -0
  60. {robotframework_tablelibrary-0.2.1 → robotframework_tablelibrary-0.3.1}/tests/utest/keywords/test_keywords_init.py +0 -0
  61. {robotframework_tablelibrary-0.2.1 → robotframework_tablelibrary-0.3.1}/tests/utest/keywords/test_keywords_modifier.py +0 -0
  62. {robotframework_tablelibrary-0.2.1 → robotframework_tablelibrary-0.3.1}/tests/utest/keywords/test_keywords_writer.py +0 -0
  63. {robotframework_tablelibrary-0.2.1 → robotframework_tablelibrary-0.3.1}/tests/utest/test_configuration.py +0 -0
  64. {robotframework_tablelibrary-0.2.1 → robotframework_tablelibrary-0.3.1}/tests/utest/test_getter.py +0 -0
  65. {robotframework_tablelibrary-0.2.1 → robotframework_tablelibrary-0.3.1}/tests/utest/test_utils_file_access.py +0 -0
  66. {robotframework_tablelibrary-0.2.1 → robotframework_tablelibrary-0.3.1}/tests/utest/test_utils_file_reader.py +0 -0
  67. {robotframework_tablelibrary-0.2.1 → robotframework_tablelibrary-0.3.1}/tests/utest/test_utils_file_system.py +0 -0
  68. {robotframework_tablelibrary-0.2.1 → robotframework_tablelibrary-0.3.1}/tests/utest/test_utils_file_writer.py +0 -0
  69. {robotframework_tablelibrary-0.2.1 → robotframework_tablelibrary-0.3.1}/tests/utest/test_utils_init.py +0 -0
  70. {robotframework_tablelibrary-0.2.1 → robotframework_tablelibrary-0.3.1}/tests/utest/test_utils_settings.py +0 -0
  71. {robotframework_tablelibrary-0.2.1 → robotframework_tablelibrary-0.3.1}/tests/utest/test_utils_settings_stack.py +0 -0
  72. {robotframework_tablelibrary-0.2.1 → robotframework_tablelibrary-0.3.1}/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
@@ -14,6 +14,8 @@ jobs:
14
14
  steps:
15
15
  - name: Checkout code
16
16
  uses: actions/checkout@v4
17
+ with:
18
+ fetch-depth: 0
17
19
 
18
20
  - name: Create GitHub Release
19
21
  uses: softprops/action-gh-release@v2
@@ -31,6 +33,8 @@ jobs:
31
33
  steps:
32
34
  - name: Checkout code
33
35
  uses: actions/checkout@v4
36
+ with:
37
+ fetch-depth: 0
34
38
 
35
39
  - name: Set up Python
36
40
  uses: actions/setup-python@v5
@@ -9,6 +9,7 @@ __pycache__/
9
9
  # Distribution / packaging
10
10
  .Python
11
11
  build/
12
+ src/Tables/_version.py
12
13
  develop-eggs/
13
14
  dist/
14
15
  downloads/
@@ -20,19 +20,19 @@ In the ``root directory`` of this repository you will find a script called ``cre
20
20
  When executing this script, it expects exactly one argument: the new name of the release / tag.
21
21
  Here you need to provide always the syntax ``X.X.X``, e.g. ``0.1.9``, ``0.5.0`` or ``1.0.0``.
22
22
 
23
- The script will do two things automatically for you:
24
- 1. Updating the version string in the **[\_\_about\_\_.py](src/Tables/__about__.py)**
25
- 2. Creating & Pushing a new tag to GitHub.
23
+ The package version is read from the Git tag. No version file needs to be changed or committed. Create and push a tag from the desired commit:
26
24
 
27
- Execute the following command to create a new release:
28
25
  ```
29
26
  cd <project-root-directory>
30
- ./create_release.sh 1.0.0
27
+ git checkout main
28
+ git pull origin main
29
+ git tag -a v1.0.0 -m "Release v1.0.0"
30
+ git push origin v1.0.0
31
31
  ```
32
32
 
33
- After executing the script, three pipeline jobs are getting triggered automatically:
33
+ After pushing the tag, three pipeline jobs are triggered automatically:
34
34
  1. First job creates a new ``Release`` in github with the name of the created ``Tag``.
35
- 2. Second job uploads the new wheel package to ``PyPi`` with the ``__version__`` from the ``__about__.py`` file.
35
+ 2. Second job builds the wheel with the version from the Git tag and uploads it to ``PyPi``.
36
36
  3. Third job generates a new keyword documentation via ``libdoc`` on the main branch, but pushes it to the ``gh_pages`` where it is available as public ``GitHub Page``.
37
37
 
38
38
  ### *Backup* - Manual Version Bump & Tag Creation
@@ -40,29 +40,21 @@ After executing the script, three pipeline jobs are getting triggered automatica
40
40
  If the preferred way using the ``create_release.sh`` script does not work, you can also manually create a new release.
41
41
  Therefore, execute the following steps.
42
42
 
43
- #### 1. Increase Version
43
+ #### Create a new tag
44
44
 
45
- Open the file **[\_\_about\_\_.py](src/Tables/__about__.py)** and increase the version (``0.0.5 = major.minor.path``) before building & uploading the new python wheel package!
46
-
47
- The new version **must be unique** & **not already existing** in ``PyPi`` & ``GitHub Releases``!!!
48
-
49
- Commit & push the changed version into the ``main`` branch of the repository.
50
-
51
- #### 2. Create new Tag
52
-
53
- Now, create a new tag from the main branch with the syntax ``vX.X.X`` -> example: ``v1.0.5``.
45
+ Create a new tag from the desired commit with the syntax ``vX.X.X``. The version must be unique in PyPI and GitHub Releases.
54
46
 
55
47
  Use the following commands to create & push the tag:
56
48
  ```
57
- git tag v0.0.5
58
- git push origin v0.0.5
49
+ git tag -a v1.0.0 -m "Release v1.0.0"
50
+ git push origin v1.0.0
59
51
  ```
60
52
 
61
- #### 2.1. Creating GitHub Release & Deploy Wheel Package to PyPi
53
+ #### Creating GitHub Release & Deploy Wheel Package to PyPi
62
54
 
63
55
  After pushing the new tag, three pipeline jobs are getting triggered automatically:
64
56
  1. First job creates a new ``Release`` in github with the name of the created ``Tag``.
65
- 2. Second job uploads the new wheel package to ``PyPi`` with the ``__version__`` from the ``__about__.py`` file.
57
+ 2. Second job builds the wheel with the version from the Git tag and uploads it to ``PyPi``.
66
58
  3. Third job generates a new keyword documentation via ``libdoc`` on the main branch, but pushes it to the ``gh_pages`` where it is available as public ``GitHub Page``.
67
59
 
68
60
  ## Installing Dev Dependencies
@@ -97,6 +89,24 @@ hatch run dev:atest
97
89
  hatch run dev:utest
98
90
  ```
99
91
 
92
+ ### Execute Tests Against the Version Matrix
93
+
94
+ 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.
95
+
96
+ Run all unit and acceptance tests against every matrix combination:
97
+
98
+ ```shell
99
+ hatch run test:all
100
+ ```
101
+
102
+ To test one combination only, for example Python 3.14 with Robot Framework 7.4.2:
103
+
104
+ ```shell
105
+ hatch run test.py3.14-7.4.2:all
106
+ ```
107
+
108
+ Use `hatch env show` to list the generated matrix environments.
109
+
100
110
  ### Generate Docs via libdoc
101
111
 
102
112
  ```shell
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.4
1
+ Metadata-Version: 2.5
2
2
  Name: robotframework-tablelibrary
3
- Version: 0.2.1
3
+ Version: 0.3.1
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
@@ -0,0 +1,32 @@
1
+ #!/usr/bin/env bash
2
+ set -e
3
+
4
+ # Script takes one argument: e.g. 0.1.0 or 1.0.0
5
+ if [ -z "$1" ]; then
6
+ echo "❌ Usage: $0 <new_version>"
7
+ exit 1
8
+ fi
9
+
10
+ NEW_VERSION="$1"
11
+ TAG="v${NEW_VERSION}"
12
+
13
+ if git rev-parse "$TAG" >/dev/null 2>&1; then
14
+ echo "❌ Tag '${TAG}' already exists locally."
15
+ exit 1
16
+ fi
17
+
18
+ if git ls-remote --exit-code --tags origin "refs/tags/${TAG}" >/dev/null 2>&1; then
19
+ echo "❌ Tag '${TAG}' already exists on origin."
20
+ exit 1
21
+ fi
22
+
23
+ if ! git diff --quiet || ! git diff --cached --quiet; then
24
+ echo "❌ Working tree has uncommitted changes. Commit or stash them before creating a release."
25
+ exit 1
26
+ fi
27
+
28
+ echo "🏷️ Creating '${TAG}' on the current commit"
29
+ git tag -a "$TAG" -m "Release ${TAG}"
30
+ git push origin "$TAG"
31
+
32
+ echo "✅ Tag '${TAG}' created and pushed. GitHub Actions will build and publish the release."
@@ -1,5 +1,5 @@
1
1
  [build-system]
2
- requires = ["hatchling"]
2
+ requires = ["hatchling", "hatch-vcs"]
3
3
  build-backend = "hatchling.build"
4
4
 
5
5
  [project]
@@ -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",
@@ -58,7 +60,10 @@ only-include = ["src/Tables"]
58
60
  sources = ["src"]
59
61
 
60
62
  [tool.hatch.version]
61
- path = "src/Tables/__about__.py"
63
+ source = "vcs"
64
+
65
+ [tool.hatch.build.hooks.vcs]
66
+ version-file = "src/Tables/_version.py"
62
67
 
63
68
  [tool.hatch.build]
64
69
  dev-mode-dirs = ["src"]
@@ -70,12 +75,28 @@ extra-dependencies = [
70
75
  [tool.hatch.envs.types.scripts]
71
76
  check = "mypy --install-types --non-interactive {args:src/Tables tests}"
72
77
 
78
+ [tool.hatch.envs.test]
79
+ dependencies = [
80
+ "pytest",
81
+ "pytest-cov",
82
+ "robotframework=={matrix:robotframework}",
83
+ ]
84
+
85
+ [tool.hatch.envs.test.scripts]
86
+ atest = "robot tests/atest/"
87
+ utest = "pytest --cov=Tables --cov-report=term-missing --cov-branch --cov-fail-under=95"
88
+ all = ["utest", "atest"]
89
+
90
+ [[tool.hatch.envs.test.matrix]]
91
+ python = ["3.10", "3.11", "3.12", "3.13", "3.14"]
92
+ robotframework = ["7.0", "7.4.2"]
93
+
73
94
  [tool.coverage.run]
74
95
  source_pkgs = ["Tables", "tests"]
75
96
  branch = true
76
97
  parallel = true
77
98
  omit = [
78
- "src/Tables/__about__.py",
99
+ "src/Tables/_version.py",
79
100
  ]
80
101
 
81
102
  [tool.coverage.paths]
@@ -103,6 +124,8 @@ docs = "libdoc src/Tables results/keyword_docs.html"
103
124
 
104
125
  [tool.mypy]
105
126
  python_version = "3.10"
127
+ exclude = '(^|/)\.venv(/|$)'
128
+ follow_imports = "skip"
106
129
  ignore_missing_imports = true
107
130
  no_implicit_optional = true
108
131
  strict_optional = true
@@ -7,7 +7,7 @@ from robotlibcore import HybridCore
7
7
 
8
8
  from Tables.utils.settings_stack import Scope, SettingsStack
9
9
 
10
- from .__about__ import __version__
10
+ from ._version import __version__
11
11
  from .keywords import (
12
12
  Configuration,
13
13
  Getter,
@@ -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():
@@ -0,0 +1,24 @@
1
+ # file generated by vcs-versioning
2
+ # don't change, don't track in version control
3
+ from __future__ import annotations
4
+
5
+ __all__ = [
6
+ "__version__",
7
+ "__version_tuple__",
8
+ "version",
9
+ "version_tuple",
10
+ "__commit_id__",
11
+ "commit_id",
12
+ ]
13
+
14
+ version: str
15
+ __version__: str
16
+ __version_tuple__: tuple[int | str, ...]
17
+ version_tuple: tuple[int | str, ...]
18
+ commit_id: str | None
19
+ __commit_id__: str | None
20
+
21
+ __version__ = version = '0.3.1'
22
+ __version_tuple__ = version_tuple = (0, 3, 1)
23
+
24
+ __commit_id__ = commit_id = None
@@ -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)."""
@@ -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: