File: tests_plugin_xlsx.py

package info (click to toggle)
rows 0.5.0~dev0~1~1d5a326-1
  • links: PTS, VCS
  • area: main
  • in suites: trixie
  • size: 2,340 kB
  • sloc: python: 12,672; sh: 117; makefile: 67
file content (224 lines) | stat: -rw-r--r-- 9,557 bytes parent folder | download | duplicates (2)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
# coding: utf-8

# Copyright 2014-2025 Álvaro Justen <https://github.com/turicas/rows/>
#    This program is free software: you can redistribute it and/or modify it under the terms of the GNU Lesser General
#    Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option)
#    any later version.
#    This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied
#    warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for
#    more details.
#    You should have received a copy of the GNU Lesser General Public License along with this program.  If not, see
#    <http://www.gnu.org/licenses/>.

from __future__ import unicode_literals

import datetime
import tempfile
import unittest
from collections import OrderedDict
from decimal import Decimal
from io import BytesIO

import mock
import pytest

import rows
import tests.utils as utils
from rows.utils import Source

ALIAS_IMPORT, ALIAS_EXPORT = rows.import_from_xlsx, rows.export_to_xlsx  # Lazy functions (just aliases)

class PluginXlsxTestCase(utils.RowsTestMixIn, unittest.TestCase):

    plugin_name = "xlsx"
    file_extension = "xlsx"
    filename = "tests/data/all-field-types.xlsx"
    expected_meta = {
        "imported_from": "xlsx",
        "name": "Sheet1",
        "source": Source(uri=filename, plugin_name=plugin_name, encoding=None),
    }

    def get_temp_filename(self):
        temp = tempfile.NamedTemporaryFile(suffix="." + self.file_extension, delete=False)
        filename = temp.name
        temp.close()
        self.files_to_delete.append(filename)
        return filename

    def test_imports(self):
        # Force the plugin to load
        original_import, original_export = rows.plugins.xlsx.import_from_xlsx, rows.plugins.xlsx.export_to_xlsx
        assert id(ALIAS_IMPORT) != id(original_import)
        assert id(ALIAS_EXPORT) != id(original_export)
        new_alias_import, new_alias_export = rows.import_from_xlsx, rows.export_to_xlsx
        assert id(new_alias_import) == id(original_import)  # Function replaced with loaded one
        assert id(new_alias_export) == id(original_export)  # Function replaced with loaded one

    @mock.patch("rows.plugins.utils.create_table")
    def test_import_from_xlsx_uses_create_table(self, mocked_create_table):
        mocked_create_table.return_value = 42
        kwargs = {"encoding": "iso-8859-15", "some_key": 123, "other": 456}
        result = rows.import_from_xlsx(self.filename, **kwargs)
        assert mocked_create_table.called
        assert mocked_create_table.call_count == 1
        assert result == 42

    @mock.patch("rows.plugins.utils.create_table")
    def test_import_from_xlsx_retrieve_desired_data(self, mocked_create_table):
        mocked_create_table.return_value = 42

        # import using filename
        rows.import_from_xlsx(self.filename)
        args, kwargs = mocked_create_table.call_args_list[0]
        args = [list(x) for x in args]
        self.assert_create_table_data((args, kwargs), expected_meta=self.expected_meta)

        # import using fobj
        with open(self.filename, "rb") as fobj:
            rows.import_from_xlsx(fobj)
            args, kwargs = mocked_create_table.call_args_list[1]
            args = [list(x) for x in args]
            self.assert_create_table_data((args, kwargs), expected_meta=self.expected_meta)

    def test_export_to_xlsx_filename(self):
        filename = self.get_temp_filename()
        rows.export_to_xlsx(utils.table, filename)

        table = rows.import_from_xlsx(filename)
        self.assert_table_equal(table, utils.table)

        export_in_memory = rows.export_to_xlsx(utils.table, None)
        result_fobj = BytesIO()
        result_fobj.write(export_in_memory)
        result_fobj.seek(0)
        result_table = rows.import_from_xlsx(result_fobj)
        self.assert_table_equal(result_table, utils.table)

    def test_export_to_xlsx_fobj_binary(self):
        filename = self.get_temp_filename()
        fobj = open(filename, "wb")
        result = rows.export_to_xlsx(utils.table, fobj)
        assert result is fobj
        assert not fobj.closed
        fobj.close()
        # TODO: test file contents instead of this side-effect
        table = rows.import_from_xlsx(filename)
        self.assert_table_equal(table, utils.table)

    def test_export_to_xlsx_fobj_text(self):
        filename = self.get_temp_filename()
        fobj = open(filename, "w")
        with pytest.raises(ValueError, match="export_to_xlsx must receive a file-object open in binary mode"):
            rows.export_to_xlsx(utils.table, fobj)

    @mock.patch("rows.plugins.utils.prepare_to_export")
    def test_export_to_xlsx_uses_prepare_to_export(self, mocked_prepare_to_export):
        filename = self.get_temp_filename()

        kwargs = {"test": 123, "parameter": 3.14}
        mocked_prepare_to_export.return_value = iter([utils.table.fields.keys()])

        rows.export_to_xlsx(utils.table, filename, **kwargs)
        assert mocked_prepare_to_export.called
        assert mocked_prepare_to_export.call_count == 1

        call = mocked_prepare_to_export.call_args
        assert call[0] == (utils.table,)
        assert call[1] == kwargs

    def test_issue_168(self):
        filename = self.get_temp_filename()

        table = rows.Table(fields=OrderedDict([("jsoncolumn", rows.fields.JSONField)]))
        table.append({"jsoncolumn": '{"python": 42}'})
        rows.export_to_xlsx(table, filename)

        table2 = rows.import_from_xlsx(filename)
        self.assert_table_equal(table, table2)

    @mock.patch("rows.plugins.utils.create_table")
    def test_start_and_end_row(self, mocked_create_table):
        rows.import_from_xlsx(
            self.filename, start_row=6, end_row=8, start_column=4, end_column=7
        )
        assert mocked_create_table.called
        assert mocked_create_table.call_count == 1
        call_args = mocked_create_table.call_args_list[0]
        expected_data = [
            [4.56, 4.56, "12%", datetime.datetime(2050, 1, 2, 0, 0)],
            [7.89, 7.89, "13.64%", datetime.datetime(2015, 8, 18, 0, 0)],
            [9.87, 9.87, "13.14%", datetime.datetime(2015, 3, 4, 0, 0)],
        ]
        assert expected_data == list(call_args[0][0])

    def test_issue_290_can_read_sheet(self):
        rows.import_from_xlsx("tests/data/text_in_percent_cell.xlsx")
        # Before fixing the first part of #290, this would simply crash
        assert True

    def test_issue_290_one_hundred_read_as_1(self):
        result = rows.import_from_xlsx("tests/data/text_in_percent_cell.xlsx")
        # As this test is written, file numeric file contents on first column are
        # 100%, 23.20%, 1.00%, 10.00%, 100.00%
        assert result[0][0] == Decimal("1")
        assert result[1][0] == Decimal("0.2320")
        assert result[2][0] == Decimal("0.01")
        assert result[3][0] == Decimal("0.1")
        assert result[4][0] == Decimal("1")

    def test_issue_290_textual_value_in_percent_col_is_preserved(self):
        result = rows.import_from_xlsx("tests/data/text_in_percent_cell.xlsx")
        # As this test is written, file contents on first column are
        # 100%, 23.20%, 1.00%, 10.00%, 100.00%
        assert result[5][1] == "text"

    # TODO: add test when sheet.min_row/max_row/min_col/max_col is None
    # (happens when file is downloaded from Google Spreadsheets).

    def test_define_sheet_name(self):
        define_sheet_name = rows.plugins.xlsx.define_sheet_name

        assert define_sheet_name(["Sheet1"]) == "Sheet2"
        assert define_sheet_name(["Test", "Test2"]) == "Sheet1"
        assert define_sheet_name(["Sheet1", "Sheet2"]) == "Sheet3"
        assert define_sheet_name(["Sheet1", "Sheet3"]) == "Sheet2"

    def test_is_existing_spreadsheet(self):
        is_existing_spreadsheet = rows.plugins.xlsx.is_existing_spreadsheet

        def get_source(filename_or_fobj):
            return Source.from_file(filename_or_fobj, mode="a+b", plugin_name="xlsx")

        filename = "this-file-doesnt-exist.xxx"
        self.files_to_delete.append(filename)
        self.assertFalse(is_existing_spreadsheet(get_source(filename)))

        filename = __file__
        self.assertFalse(is_existing_spreadsheet(get_source(filename)))

        filename = self.filename
        assert is_existing_spreadsheet(get_source(filename))

        data = BytesIO()
        with open(self.filename, mode="rb") as fobj:
            data.write(fobj.read())
        assert is_existing_spreadsheet(get_source(data))

    def test_write_multiple_sheets(self):
        filename = self.get_temp_filename()

        table1 = rows.import_from_dicts([{"f1": 1, "f2": 2}, {"f1": 3, "f2": 4}])
        table2 = rows.import_from_dicts([{"f1": -1, "f2": -2}, {"f1": -3, "f2": -4}])
        table3 = rows.import_from_dicts([{"f1": 0, "f2": 1}, {"f1": 2, "f2": 3}])

        rows.export_to_xlsx(table1, filename, sheet_name="Test1")
        rows.export_to_xlsx(table2, filename)
        rows.export_to_xlsx(table3, filename)

        result = rows.plugins.xlsx.sheet_names(filename)
        assert result == ["Test1", "Sheet1", "Sheet2"]

        assert list(table1) == list(rows.import_from_xlsx(filename, sheet_name="Test1"))
        assert list(table2) == list(rows.import_from_xlsx(filename, sheet_name="Sheet1"))
        assert list(table3) == list(rows.import_from_xlsx(filename, sheet_name="Sheet2"))