1# Licensed under the Apache License, Version 2.0 (the "License");
2# you may not use this file except in compliance with the License.
3# You may obtain a copy of the License at
4#
5#      http://www.apache.org/licenses/LICENSE-2.0
6#
7# Unless required by applicable law or agreed to in writing, software
8# distributed under the License is distributed on an "AS IS" BASIS,
9# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
10# See the License for the specific language governing permissions and
11# limitations under the License.
12
13"""
14Provides patches for some commonly used modules that enable them to work
15with pyfakefs.
16"""
17import sys
18
19try:
20    import pandas as pd
21    import pandas.io.parsers as parsers
22except ImportError:
23    parsers = None
24
25try:
26    import xlrd
27except ImportError:
28    xlrd = None
29
30try:
31    from django.core.files import locks
32except ImportError:
33    locks = None
34
35# From pandas v 1.2 onwards the python fs functions are used even when the engine
36# selected is "c". This means that we don't explicitly have to change the engine.
37patch_pandas = parsers is not None and [int(v) for v in pd.__version__.split(".")] < [
38    1,
39    2,
40    0,
41]
42
43
44def get_modules_to_patch():
45    modules_to_patch = {}
46    if xlrd is not None:
47        modules_to_patch["xlrd"] = XLRDModule
48    if locks is not None:
49        modules_to_patch["django.core.files.locks"] = FakeLocks
50    return modules_to_patch
51
52
53def get_classes_to_patch():
54    classes_to_patch = {}
55    if patch_pandas:
56        classes_to_patch["TextFileReader"] = ["pandas.io.parsers"]
57    return classes_to_patch
58
59
60def get_fake_module_classes():
61    fake_module_classes = {}
62    if patch_pandas:
63        fake_module_classes["TextFileReader"] = FakeTextFileReader
64    return fake_module_classes
65
66
67if xlrd is not None:
68
69    class XLRDModule:
70        """Patches the xlrd module, which is used as the default Excel file
71        reader by pandas. Disables using memory mapped files, which are
72        implemented platform-specific on OS level."""
73
74        def __init__(self, _):
75            self._xlrd_module = xlrd
76
77        def open_workbook(
78            self,
79            filename=None,
80            logfile=sys.stdout,
81            verbosity=0,
82            use_mmap=False,
83            file_contents=None,
84            encoding_override=None,
85            formatting_info=False,
86            on_demand=False,
87            ragged_rows=False,
88        ):
89            return self._xlrd_module.open_workbook(
90                filename,
91                logfile,
92                verbosity,
93                False,
94                file_contents,
95                encoding_override,
96                formatting_info,
97                on_demand,
98                ragged_rows,
99            )
100
101        def __getattr__(self, name):
102            """Forwards any unfaked calls to the standard xlrd module."""
103            return getattr(self._xlrd_module, name)
104
105
106if patch_pandas:
107    # we currently need to add fake modules for both the parser module and
108    # the contained text reader - maybe this can be simplified
109
110    class FakeTextFileReader:
111        fake_parsers = None
112
113        def __init__(self, filesystem):
114            if self.fake_parsers is None:
115                self.__class__.fake_parsers = ParsersModule(filesystem)
116
117        def __call__(self, *args, **kwargs):
118            return self.fake_parsers.TextFileReader(*args, **kwargs)
119
120        def __getattr__(self, name):
121            return getattr(self.fake_parsers.TextFileReader, name)
122
123    class ParsersModule:
124        def __init__(self, _):
125            self._parsers_module = parsers
126
127        class TextFileReader(parsers.TextFileReader):
128            def __init__(self, *args, **kwargs):
129                kwargs["engine"] = "python"
130                super().__init__(*args, **kwargs)
131
132        def __getattr__(self, name):
133            """Forwards any unfaked calls to the standard xlrd module."""
134            return getattr(self._parsers_module, name)
135
136
137if locks is not None:
138
139    class FakeLocks:
140        """django.core.files.locks uses low level OS functions, fake it."""
141
142        _locks_module = locks
143
144        def __init__(self, _):
145            pass
146
147        @staticmethod
148        def lock(f, flags):
149            return True
150
151        @staticmethod
152        def unlock(f):
153            return True
154
155        def __getattr__(self, name):
156            return getattr(self._locks_module, name)
157