img4it 0.0.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 (30) hide show
  1. img4it-0.0.1/License.txt +6 -0
  2. img4it-0.0.1/PKG-INFO +11 -0
  3. img4it-0.0.1/img4it.egg-info/PKG-INFO +11 -0
  4. img4it-0.0.1/img4it.egg-info/SOURCES.txt +28 -0
  5. img4it-0.0.1/img4it.egg-info/dependency_links.txt +1 -0
  6. img4it-0.0.1/img4it.egg-info/entry_points.txt +5 -0
  7. img4it-0.0.1/img4it.egg-info/namespace_packages.txt +1 -0
  8. img4it-0.0.1/img4it.egg-info/requires.txt +2 -0
  9. img4it-0.0.1/img4it.egg-info/top_level.txt +1 -0
  10. img4it-0.0.1/orangecontrib/IMG4IT/__init__.py +0 -0
  11. img4it-0.0.1/orangecontrib/IMG4IT/utils/__init__.py +0 -0
  12. img4it-0.0.1/orangecontrib/IMG4IT/widgets/OWConvertImages.py +128 -0
  13. img4it-0.0.1/orangecontrib/IMG4IT/widgets/OWPaddleOCR.py +210 -0
  14. img4it-0.0.1/orangecontrib/IMG4IT/widgets/__init__.py +0 -0
  15. img4it-0.0.1/orangecontrib/IMG4IT/widgets/designer/__init__.py +0 -0
  16. img4it-0.0.1/orangecontrib/IMG4IT/widgets/icons/__init__.py +0 -0
  17. img4it-0.0.1/orangecontrib/IMG4IT/widgets/icons_dev/__init__.py +0 -0
  18. img4it-0.0.1/orangecontrib/__init__.py +1 -0
  19. img4it-0.0.1/setup.cfg +4 -0
  20. img4it-0.0.1/setup.py +65 -0
  21. img4it-0.0.1/tests/test_class_values_context_handler.py +75 -0
  22. img4it-0.0.1/tests/test_credentials.py +76 -0
  23. img4it-0.0.1/tests/test_domain_context_handler.py +401 -0
  24. img4it-0.0.1/tests/test_gui.py +140 -0
  25. img4it-0.0.1/tests/test_matplotlib_export.py +43 -0
  26. img4it-0.0.1/tests/test_perfect_domain_context_handler.py +148 -0
  27. img4it-0.0.1/tests/test_scatterplot_density.py +59 -0
  28. img4it-0.0.1/tests/test_settings_handler.py +27 -0
  29. img4it-0.0.1/tests/test_widgets_outputs.py +29 -0
  30. img4it-0.0.1/tests/test_workflows.py +80 -0
@@ -0,0 +1,6 @@
1
+ THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT ANY WARRANTY WHATSOEVER.
2
+
3
+ If you use or redistribute this software, you are permitted to do so
4
+ under the terms of GNU [GPL-3.0]+ license.
5
+
6
+ [GPL-3.0]: https://www.gnu.org/licenses/gpl-3.0.en.html
img4it-0.0.1/PKG-INFO ADDED
@@ -0,0 +1,11 @@
1
+ Metadata-Version: 2.1
2
+ Name: img4it
3
+ Version: 0.0.1
4
+ Summary: Exploit computer vision technology with Orange Data Mining !
5
+ Home-page:
6
+ Author: Orange community
7
+ Author-email:
8
+ Keywords: orange3 add-on
9
+ License-File: License.txt
10
+ Requires-Dist: aait
11
+ Requires-Dist: paddlepaddle==2.6.2
@@ -0,0 +1,11 @@
1
+ Metadata-Version: 2.1
2
+ Name: img4it
3
+ Version: 0.0.1
4
+ Summary: Exploit computer vision technology with Orange Data Mining !
5
+ Home-page:
6
+ Author: Orange community
7
+ Author-email:
8
+ Keywords: orange3 add-on
9
+ License-File: License.txt
10
+ Requires-Dist: aait
11
+ Requires-Dist: paddlepaddle==2.6.2
@@ -0,0 +1,28 @@
1
+ License.txt
2
+ setup.py
3
+ img4it.egg-info/PKG-INFO
4
+ img4it.egg-info/SOURCES.txt
5
+ img4it.egg-info/dependency_links.txt
6
+ img4it.egg-info/entry_points.txt
7
+ img4it.egg-info/namespace_packages.txt
8
+ img4it.egg-info/requires.txt
9
+ img4it.egg-info/top_level.txt
10
+ orangecontrib/__init__.py
11
+ orangecontrib/IMG4IT/__init__.py
12
+ orangecontrib/IMG4IT/utils/__init__.py
13
+ orangecontrib/IMG4IT/widgets/OWConvertImages.py
14
+ orangecontrib/IMG4IT/widgets/OWPaddleOCR.py
15
+ orangecontrib/IMG4IT/widgets/__init__.py
16
+ orangecontrib/IMG4IT/widgets/designer/__init__.py
17
+ orangecontrib/IMG4IT/widgets/icons/__init__.py
18
+ orangecontrib/IMG4IT/widgets/icons_dev/__init__.py
19
+ tests/test_class_values_context_handler.py
20
+ tests/test_credentials.py
21
+ tests/test_domain_context_handler.py
22
+ tests/test_gui.py
23
+ tests/test_matplotlib_export.py
24
+ tests/test_perfect_domain_context_handler.py
25
+ tests/test_scatterplot_density.py
26
+ tests/test_settings_handler.py
27
+ tests/test_widgets_outputs.py
28
+ tests/test_workflows.py
@@ -0,0 +1,5 @@
1
+ [orange.widgets]
2
+ Advanced Artificial Intelligence Tools = orangecontrib.AAIT.widgets
3
+
4
+ [orange.widgets.tutorials]
5
+ AAIT Tutorials = orangecontrib.AAIT.tutorials
@@ -0,0 +1 @@
1
+ orangecontrib
@@ -0,0 +1,2 @@
1
+ aait
2
+ paddlepaddle==2.6.2
@@ -0,0 +1 @@
1
+ orangecontrib
File without changes
File without changes
@@ -0,0 +1,128 @@
1
+ import numpy as np
2
+ import os
3
+ import zipfile
4
+ from PIL import Image
5
+ import pillow_heif
6
+ import Orange
7
+ from Orange.data import Table, Domain, StringVariable
8
+ from pathlib import Path
9
+ from Orange.widgets.widget import OWWidget, Input, Output
10
+ from AnyQt.QtWidgets import QApplication
11
+
12
+ class OWConvertImages(OWWidget):
13
+ name = "Convert Images"
14
+ description = "Takes one or more folders as input. In these folders, if a .zip file is found, extract the images it contains, convert them to JPG, and reduce their resolution to decrease the image file size."
15
+ icon = "icons/resize-picture.svg"
16
+ if "site-packages/Orange/widgets" in os.path.dirname(os.path.abspath(__file__)).replace("\\", "/"):
17
+ icon = "icons_dev/resize-picture.svg"
18
+ priority = 3000
19
+
20
+ class Inputs:
21
+ data = Input("Data", Orange.data.Table)
22
+
23
+ @Inputs.data
24
+ def set_data(self, in_data):
25
+ self.data = in_data
26
+ if self.data is None:
27
+ return
28
+ if "folders_path" in in_data.domain:
29
+ self.folders_path = in_data.get_column("folders_path")
30
+ elif "folder_path" in in_data.domain:
31
+ self.folders_path = in_data.get_column("folder_path")
32
+ if "file_path" in in_data.domain:
33
+ self.path_file = in_data.get_column("file_path")
34
+ else:
35
+ if self.folders_path is None:
36
+ self.error("No folder provided.")
37
+ return
38
+ path = Path(str(self.folders_path[0]))
39
+ self.path_file = path.parent
40
+ self.run()
41
+
42
+ class Outputs:
43
+ data = Output("Data", Orange.data.Table)
44
+
45
+ def __init__(self):
46
+ super().__init__()
47
+ self.data = None
48
+ self.folders_path = None
49
+ self.path_file = None
50
+ self.MAX_SIZE = (1000, 1000)
51
+ self.run()
52
+
53
+ def trouver_fichiers_zip(self,dossier):
54
+ chemin_complet = None
55
+ for racine, dossiers, fichiers in os.walk(dossier):
56
+ for fichier in fichiers:
57
+ if fichier.lower().endswith('.zip'):
58
+ chemin_complet = os.path.join(racine, fichier)
59
+ return chemin_complet
60
+
61
+ def run(self):
62
+ self.error("")
63
+ self.warning("")
64
+
65
+ if self.data is None:
66
+ return
67
+
68
+ if self.folders_path is None or self.path_file is None:
69
+ self.error("No folder provided.")
70
+ return
71
+
72
+ # Fonctionnel pour confideo
73
+ # a modifier cette fonction encore la fonction de dezip à l'intérieur
74
+ # il faudrait modifier pour enlever le début et ne laisser que la partie convertion d'image
75
+ image_paths = []
76
+ pillow_heif.register_heif_opener()
77
+ # Extraction et collecte des images
78
+ for file_path in self.folders_path:
79
+ img = []
80
+ path = self.trouver_fichiers_zip(file_path)
81
+ if path == None:
82
+ pass
83
+ extract_path = file_path + "/" + "temp_images"
84
+ os.makedirs(extract_path, exist_ok=True)
85
+ ext = os.path.splitext(str(path))[1].lower()
86
+ if ext == ".zip":
87
+ with zipfile.ZipFile(path, "r") as zip_ref:
88
+ zip_ref.extractall(extract_path)
89
+ for root, dirs, files in os.walk(extract_path):
90
+ for fname in files:
91
+ if fname.lower().endswith((".png", ".jpg", ".jpeg", ".tiff", ".tif", ".heif", ".heic")):
92
+ img.append(os.path.join(root, fname))
93
+ elif ext in [".png", ".jpg", ".jpeg", ".tiff", ".tif", ".heif", ".heic"]:
94
+ image_paths.append(path)
95
+ image_paths.append(img)
96
+
97
+ converted_path = str(self.path_file) + "/temp_images/converted"
98
+ os.makedirs(converted_path, exist_ok=True)
99
+ new_image_paths = []
100
+
101
+ for image_path in image_paths:
102
+ for img_path in image_path:
103
+ ext = os.path.splitext(img_path)[1].lower()
104
+ if ext in [".png", ".jpg", ".jpeg", ".tiff", ".tif", ".heif", ".heic"]:
105
+ try:
106
+ image = Image.open(img_path)
107
+ if image.mode != 'RGB':
108
+ image = image.convert('RGB')
109
+ image.thumbnail(self.MAX_SIZE)
110
+ new_filename = os.path.splitext(os.path.basename(img_path))[0] + ".jpg"
111
+ new_path = os.path.join(converted_path, new_filename)
112
+ image.save(new_path, format="JPEG", quality=70)
113
+ new_image_paths.append(new_path)
114
+ # Supprimer l'original (facultatif)
115
+ # os.remove(img_path)
116
+ except Exception as e:
117
+ print(f"Erreur de conversion {img_path} : {e}")
118
+ data_metas = [[str(self.path_file), str(new_image_paths)]]
119
+ domain = Domain([], metas=[StringVariable("folder_path"), StringVariable("image_paths")])
120
+ table = Table.from_numpy(domain, np.empty((len(data_metas), 0)), metas=data_metas)
121
+ self.Outputs.data.send(table)
122
+
123
+ if __name__ == "__main__":
124
+ import sys
125
+ app = QApplication(sys.argv)
126
+ my_widget = OWConvertImages()
127
+ my_widget.show()
128
+ app.exec_()
@@ -0,0 +1,210 @@
1
+ import os
2
+ import sys
3
+ import numpy as np
4
+
5
+ import fitz
6
+ from paddleocr import PaddleOCR
7
+
8
+ import Orange.data
9
+ from Orange.data import Table, Domain, StringVariable, ContinuousVariable
10
+ from AnyQt.QtWidgets import QApplication
11
+ from Orange.widgets import widget
12
+ from Orange.widgets.utils.signals import Input, Output
13
+
14
+ if "site-packages/Orange/widgets" in os.path.dirname(os.path.abspath(__file__)).replace("\\", "/"):
15
+ from Orange.widgets.orangecontrib.AAIT.utils import thread_management, SimpleDialogQt
16
+ from Orange.widgets.orangecontrib.AAIT.utils.import_uic import uic
17
+ from Orange.widgets.orangecontrib.AAIT.utils.initialize_from_ini import apply_modification_from_python_file
18
+ from Orange.widgets.orangecontrib.AAIT.utils.MetManagement import GetFromRemote, get_local_store_path
19
+ else:
20
+ from orangecontrib.AAIT.utils import thread_management, SimpleDialogQt
21
+ from orangecontrib.AAIT.utils.import_uic import uic
22
+ from orangecontrib.AAIT.utils.initialize_from_ini import apply_modification_from_python_file
23
+ from orangecontrib.AAIT.utils.MetManagement import GetFromRemote, get_local_store_path
24
+
25
+
26
+
27
+ @apply_modification_from_python_file(filepath_original_widget=__file__)
28
+ class OWPaddleOCR(widget.OWWidget):
29
+ name = "Paddle OCR"
30
+ description = "Apply OCR on the PDF documents present in the 'path' column of the input Table"
31
+ icon = "icons/paddleocr.svg"
32
+ if "site-packages/Orange/widgets" in os.path.dirname(os.path.abspath(__file__)).replace("\\", "/"):
33
+ icon = "icons_dev/paddleocr.svg"
34
+ gui = os.path.join(os.path.dirname(os.path.abspath(__file__)), "designer/owpaddleocr.ui")
35
+ want_control_area = False
36
+ priority = 1212
37
+
38
+ class Inputs:
39
+ data = Input("Data", Orange.data.Table)
40
+
41
+ class Outputs:
42
+ data = Output("Data", Orange.data.Table)
43
+
44
+ @Inputs.data
45
+ def set_data(self, in_data):
46
+ self.data = in_data
47
+ if self.autorun:
48
+ self.run()
49
+
50
+ def __init__(self):
51
+ super().__init__()
52
+ # Qt Management
53
+ self.setFixedWidth(470)
54
+ self.setFixedHeight(300)
55
+ uic.loadUi(self.gui, self)
56
+
57
+ # Data Management
58
+ self.data = None
59
+ self.model = None
60
+ self.thread = None
61
+ self.autorun = True
62
+ self.result = None
63
+ self.load_model()
64
+ self.post_initialized()
65
+
66
+ def load_model(self):
67
+ local_store_path = get_local_store_path()
68
+ model_path = os.path.join(local_store_path, "Models", "ComputerVision", ".paddleocr", "whl")
69
+ load = True
70
+ if not os.path.exists(model_path):
71
+ load = False
72
+ self.error("PaddleOCR model is not in your computer. This widget cannot process your data.")
73
+ if not SimpleDialogQt.BoxYesNo("Model isn't in your computer. Do you want to download it from AAIT store?"):
74
+ return
75
+ try:
76
+ if 0 == GetFromRemote("Paddle OCR"):
77
+ load = True
78
+ self.error("")
79
+ except:
80
+ SimpleDialogQt.BoxError("Unable to get the Model.")
81
+ return
82
+ if load:
83
+ self.model = PaddleOCR(use_angle_cls=True, lang="fr",
84
+ det_model_dir=os.path.join(model_path, "det"),
85
+ rec_model_dir=os.path.join(model_path, "rec"),
86
+ cls_model_dir=os.path.join(model_path, "cls"),
87
+ show_log=False)
88
+ self.information("Paddle OCR model successfully loaded.")
89
+
90
+
91
+ def run(self):
92
+ # if thread is running quit
93
+ if self.thread is not None:
94
+ self.thread.safe_quit()
95
+
96
+ if self.data is None:
97
+ return
98
+
99
+ if self.model is None:
100
+ return
101
+
102
+ # Verification of in_data
103
+ self.error("")
104
+ try:
105
+ self.data.domain["path"]
106
+ except KeyError:
107
+ self.error('You need a "path" column in input data')
108
+ return
109
+
110
+ if type(self.data.domain["path"]).__name__ != 'StringVariable':
111
+ self.error('"path" column needs to be a Text')
112
+ return
113
+
114
+ # Start progress bar
115
+ self.progressBarInit()
116
+
117
+ # Connect and start thread : main function, progress, result and finish
118
+ # --> progress is used in the main function to track progress (with a callback)
119
+ # --> result is used to collect the result from main function
120
+ # --> finish is just an empty signal to indicate that the thread is finished
121
+ self.thread = thread_management.Thread(self.apply_OCR_on_Table, self.data, self.model)
122
+ self.thread.progress.connect(self.handle_progress)
123
+ self.thread.result.connect(self.handle_result)
124
+ self.thread.finish.connect(self.handle_finish)
125
+ self.thread.start()
126
+
127
+ def handle_progress(self, value: float) -> None:
128
+ self.progressBarSet(value)
129
+
130
+ def handle_result(self, result):
131
+ try:
132
+ self.result = result
133
+ self.Outputs.data.send(result)
134
+ except Exception as e:
135
+ print("An error occurred when sending out_data:", e)
136
+ self.Outputs.data.send(None)
137
+ return
138
+
139
+ def handle_finish(self):
140
+ print("Embeddings finished")
141
+ self.progressBarFinished()
142
+
143
+ def post_initialized(self):
144
+ pass
145
+
146
+
147
+ def apply_OCR_on_Table(self, table, model, progress_callback=None, argself=None):
148
+ # Copy of input data
149
+ data = table.copy()
150
+ attr_dom = list(data.domain.attributes)
151
+ metas_dom = list(data.domain.metas)
152
+ class_dom = list(data.domain.class_vars)
153
+
154
+ # Iterate on the data Table
155
+ rows = []
156
+ for i, row in enumerate(data):
157
+ # Get the rest of the data
158
+ features = [row[x] for x in attr_dom]
159
+ targets = [row[y] for y in class_dom]
160
+ metas = list(data.metas[i])
161
+ filepath = row["path"].value
162
+ result_per_page = apply_OCR_on_pdf(filepath, model)
163
+ for key, value in result_per_page.items():
164
+ new_row = features + targets + metas + [key, value]
165
+ rows.append(new_row)
166
+ if progress_callback is not None:
167
+ progress_value = float(100 * (i + 1) / len(data))
168
+ progress_callback(progress_value)
169
+ if argself is not None:
170
+ if argself.stop:
171
+ break
172
+
173
+ # Create new Domain for new columns
174
+ ocr_dom = [ContinuousVariable("Page n°"), StringVariable("OCR Extraction")]
175
+ domain = Domain(attributes=attr_dom, metas=metas_dom + ocr_dom, class_vars=class_dom)
176
+
177
+ # Create and return table
178
+ out_data = Table.from_list(domain=domain, rows=rows)
179
+ return out_data
180
+
181
+
182
+ def apply_OCR_on_pdf(filepath, model, dpi=300):
183
+ # Load the document
184
+ doc = fitz.open(filepath)
185
+ # Iterate over pages
186
+ result_per_page = {}
187
+ for page_num in range(len(doc)):
188
+ page = doc.load_page(page_num)
189
+
190
+ # Render page to a pixmap (RGB image)
191
+ mat = fitz.Matrix(dpi/72, dpi/72)
192
+ pix = page.get_pixmap(matrix=mat, colorspace=fitz.csRGB)
193
+ img = np.frombuffer(pix.samples, dtype=np.uint8).reshape(pix.height, pix.width, pix.n)
194
+
195
+ # Apply OCR
196
+ result_ocr = model.ocr(img, cls=True)[0]
197
+ full_text = ""
198
+ for line in result_ocr:
199
+ text = line[1][0]
200
+ # confidence = line[1][1] # maybe useful later
201
+ full_text += text + "\n"
202
+ result_per_page[page_num] = full_text
203
+ return result_per_page
204
+
205
+
206
+ if __name__ == "__main__":
207
+ app = QApplication(sys.argv)
208
+ my_widget = OWPaddleOCR()
209
+ my_widget.show()
210
+ app.exec_()
File without changes
@@ -0,0 +1 @@
1
+ __import__("pkg_resources").declare_namespace(__name__)
img4it-0.0.1/setup.cfg ADDED
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
img4it-0.0.1/setup.py ADDED
@@ -0,0 +1,65 @@
1
+ from setuptools import setup, find_packages
2
+
3
+ # Nom du package PyPI ('pip install NAME')
4
+ NAME = "img4it"
5
+
6
+ # Version du package PyPI
7
+ VERSION = "0.0.1" # la version doit être supérieure à la précédente sinon la publication sera refusée
8
+
9
+ # Facultatif / Adaptable à souhait
10
+ AUTHOR = "Orange community"
11
+ AUTHOR_EMAIL = ""
12
+ URL = ""
13
+ DESCRIPTION = "Exploit computer vision technology with Orange Data Mining !"
14
+ LICENSE = ""
15
+
16
+ # 'orange3 add-on' permet de rendre l'addon téléchargeable via l'interface addons d'Orange
17
+ KEYWORDS = ["orange3 add-on",]
18
+
19
+ # Tous les packages python existants dans le projet (avec un __ini__.py)
20
+ PACKAGES = find_packages()
21
+ PACKAGES = [pack for pack in PACKAGES if "IMG4IT" in pack]
22
+ PACKAGES.append("orangecontrib")
23
+ print(PACKAGES)
24
+
25
+
26
+
27
+ # Fichiers additionnels aux fichiers .py (comme les icons ou des .ows)
28
+ PACKAGE_DATA = {
29
+ }
30
+ # /!\ les noms de fichier 'orangecontrib.hkh_bot.widgets' doivent correspondre à l'arborescence
31
+
32
+ # Dépendances
33
+
34
+ INSTALL_REQUIRES = [
35
+ "aait",
36
+ "paddlepaddle==2.6.2"]
37
+
38
+
39
+ # Spécifie le dossier contenant les widgets et le nom de section qu'aura l'addon sur Orange
40
+ ENTRY_POINTS = {
41
+ "orange.widgets": (
42
+ "Advanced Artificial Intelligence Tools = orangecontrib.AAIT.widgets",
43
+ ),
44
+ "orange.widgets.tutorials": (
45
+ "AAIT Tutorials = orangecontrib.AAIT.tutorials",
46
+ )
47
+ }
48
+ # /!\ les noms de fichier 'orangecontrib.hkh_bot.widgets' doivent correspondre à l'arborescence
49
+
50
+ NAMESPACE_PACKAGES = ["orangecontrib"]
51
+
52
+ setup(name=NAME,
53
+ version=VERSION,
54
+ author=AUTHOR,
55
+ author_email=AUTHOR_EMAIL,
56
+ url=URL,
57
+ description=DESCRIPTION,
58
+ license=LICENSE,
59
+ keywords=KEYWORDS,
60
+ packages=PACKAGES,
61
+ package_data=PACKAGE_DATA,
62
+ install_requires=INSTALL_REQUIRES,
63
+ entry_points=ENTRY_POINTS,
64
+ namespace_packages=NAMESPACE_PACKAGES,
65
+ )
@@ -0,0 +1,75 @@
1
+ from unittest import TestCase
2
+ from unittest.mock import Mock
3
+ from Orange.data import DiscreteVariable, Domain
4
+ from Orange.data import ContinuousVariable
5
+ from Orange.widgets.settings import ContextSetting, ClassValuesContextHandler
6
+ from Orange.widgets.utils import vartype
7
+
8
+ Continuous = vartype(ContinuousVariable("x"))
9
+ Discrete = vartype(DiscreteVariable("x"))
10
+
11
+
12
+ class TestClassValuesContextHandler(TestCase):
13
+ def setUp(self):
14
+ self.domain = Domain(
15
+ attributes=[ContinuousVariable('c1'),
16
+ DiscreteVariable('d1', values='abc'),
17
+ DiscreteVariable('d2', values='def')],
18
+ class_vars=[DiscreteVariable('d3', values='ghi')],
19
+ metas=[ContinuousVariable('c2'),
20
+ DiscreteVariable('d4', values='jkl')]
21
+ )
22
+ self.args = (self.domain,
23
+ {'c1': Continuous, 'd1': Discrete,
24
+ 'd2': Discrete, 'd3': Discrete},
25
+ {'c2': Continuous, 'd4': Discrete, })
26
+ self.handler = ClassValuesContextHandler()
27
+ self.handler.read_defaults = lambda: None
28
+
29
+ def test_open_context(self):
30
+ self.handler.bind(SimpleWidget)
31
+ context = Mock(
32
+ classes=['g', 'h', 'i'], values=dict(
33
+ text='u',
34
+ with_metas=[('d1', Discrete), ('d2', Discrete)]
35
+ ))
36
+ self.handler.global_contexts = \
37
+ [Mock(classes=[], values={}), context, Mock(classes=[], values={})]
38
+
39
+ widget = SimpleWidget()
40
+ self.handler.initialize(widget)
41
+ self.handler.open_context(widget, self.args[0].class_var)
42
+ self.assertEqual(widget.text, 'u')
43
+ self.assertEqual(widget.with_metas, [('d1', Discrete),
44
+ ('d2', Discrete)])
45
+
46
+ def test_open_context_with_no_match(self):
47
+ self.handler.bind(SimpleWidget)
48
+ context = Mock(
49
+ classes=['g', 'h', 'i'], values=dict(
50
+ text='u',
51
+ with_metas=[('d1', Discrete), ('d2', Discrete)]
52
+ ))
53
+ self.handler.global_contexts = \
54
+ [Mock(classes=[], values={}), context, Mock(classes=(), values={})]
55
+ widget = SimpleWidget()
56
+ self.handler.initialize(widget)
57
+ widget.text = 'u'
58
+
59
+ self.handler.open_context(widget, self.args[0][1])
60
+
61
+ context = widget.current_context
62
+ self.assertEqual(context.classes, ('a', 'b', 'c'))
63
+ self.assertEqual(widget.text, 'u')
64
+ self.assertEqual(widget.with_metas, [])
65
+
66
+
67
+ class SimpleWidget:
68
+ text = ContextSetting("")
69
+ with_metas = ContextSetting([])
70
+ required = ContextSetting("", required=ContextSetting.REQUIRED)
71
+
72
+ def retrieveSpecificSettings(self):
73
+ pass
74
+ def storeSpecificSettings(self):
75
+ pass
@@ -0,0 +1,76 @@
1
+ import unittest
2
+ from unittest.mock import patch
3
+
4
+ import keyring
5
+ import keyring.errors
6
+ import keyring.backend
7
+
8
+ from Orange.widgets.credentials import CredentialManager
9
+
10
+
11
+ # minimal in-memory keyring implementation so the test is not dependent on
12
+ # the system config/services.
13
+ class Keyring(keyring.backend.KeyringBackend):
14
+ priority = 0
15
+
16
+ def __init__(self):
17
+ self.__store = {}
18
+
19
+ def set_password(self, service, username, password=None):
20
+ self.__store[(service, username)] = password
21
+
22
+ def get_password(self, service, username):
23
+ return self.__store.get((service, username), None)
24
+
25
+ def delete_password(self, service, username):
26
+ try:
27
+ del self.__store[service, username]
28
+ except KeyError:
29
+ raise keyring.errors.PasswordDeleteError()
30
+
31
+
32
+ class TestCredentialManager(unittest.TestCase):
33
+ def setUp(self):
34
+ self._ring = keyring.get_keyring()
35
+ keyring.set_keyring(Keyring())
36
+ self.cm = CredentialManager('Orange')
37
+
38
+ def tearDown(self):
39
+ keyring.set_keyring(self._ring)
40
+
41
+ def test_credential_manager(self):
42
+ cm = CredentialManager('Orange')
43
+ cm.key = 'Foo'
44
+ self.assertEqual(cm.key, 'Foo')
45
+ del cm.key
46
+ self.assertEqual(cm.key, None)
47
+
48
+ def test_set_password(self):
49
+ """
50
+ Handle error when setting password fails.
51
+ GH-2354
52
+ """
53
+ with patch("keyring.set_password", side_effect=Exception), \
54
+ patch("Orange.widgets.credentials.log.exception") as log:
55
+ self.cm.key = ""
56
+ log.assert_called()
57
+
58
+ def test_delete_password(self):
59
+ """
60
+ Handling error when deleting password fails
61
+ GH-2354
62
+ """
63
+ with patch("keyring.delete_password", side_effect=Exception), \
64
+ patch("Orange.widgets.credentials.log.exception") as log:
65
+ del self.cm.key
66
+ log.assert_called()
67
+
68
+ def test_get_password(self):
69
+ """
70
+ Handling errors when getting password fails.
71
+ GH-2354
72
+ """
73
+ with patch("keyring.get_password", side_effect=Exception), \
74
+ patch("Orange.widgets.credentials.log.exception") as log:
75
+ self.assertEqual(self.cm.key, None)
76
+ log.assert_called()