We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
Fitter preprocessors
<!--
This is an issue template. Please fill in the relevant details in the
sections below.
-->

##### Orange version
<!-- From menu _Help→About→Version_ or code `Orange.version.full_version` -->
3.4.0

##### Expected behavior
Learners use preprocessors.


##### Actual behavior
Learners extending the Fitter base class do not use preprocessors.


##### Steps to reproduce the behavior
Use a learner on e.g. hearth_disease data set


##### Additional info (worksheets, data, screenshots, ...)




--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `Orange/modelling/base.py`
Content:
```
1 from Orange.base import Learner, Model
2 
3 
4 class FitterMeta(type):
5     """Ensure that each subclass of the `Fitter` class overrides the `__fits__`
6     attribute with a valid value."""
7     def __new__(mcs, name, bases, attrs):
8         # Check that a fitter implementation defines a valid `__fits__`
9         if any(cls.__name__ == 'Fitter' for cls in bases):
10             fits = attrs.get('__fits__')
11             assert isinstance(fits, dict), '__fits__ must be dict instance'
12             assert fits.get('classification') and fits.get('regression'), \
13                 ('`__fits__` property does not define classification '
14                  'or regression learner. Use a simple learner if you don\'t '
15                  'need the functionality provided by Fitter.')
16         return super().__new__(mcs, name, bases, attrs)
17 
18 
19 class Fitter(Learner, metaclass=FitterMeta):
20     """Handle multiple types of target variable with one learner.
21 
22     Subclasses of this class serve as a sort of dispatcher. When subclassing,
23     we provide a `dict` which contain actual learner classes that handle
24     appropriate data types. The fitter can then be used on any data and will
25     delegate the work to the appropriate learner.
26 
27     If the learners that handle each data type require different parameters,
28     you should pass in all the possible parameters to the fitter. The fitter
29     will then determine which parameters have to be passed to individual
30     learners.
31 
32     """
33     __fits__ = None
34     __returns__ = Model
35 
36     # Constants to indicate what kind of problem we're dealing with
37     CLASSIFICATION, REGRESSION = 'classification', 'regression'
38 
39     def __init__(self, preprocessors=None, **kwargs):
40         super().__init__(preprocessors=preprocessors)
41         self.kwargs = kwargs
42         # Make sure to pass preprocessor params to individual learners
43         self.kwargs['preprocessors'] = preprocessors
44         self.__learners = {self.CLASSIFICATION: None, self.REGRESSION: None}
45 
46     def _fit_model(self, data):
47         if data.domain.has_discrete_class:
48             learner = self.get_learner(self.CLASSIFICATION)
49         else:
50             learner = self.get_learner(self.REGRESSION)
51 
52         if type(self).fit is Learner.fit:
53             return learner.fit_storage(data)
54         else:
55             X, Y, W = data.X, data.Y, data.W if data.has_weights() else None
56             return learner.fit(X, Y, W)
57 
58     def get_learner(self, problem_type):
59         """Get the learner for a given problem type.
60 
61         Returns
62         -------
63         Learner
64             The appropriate learner for the given problem type.
65 
66         """
67         # Prevent trying to access the learner when problem type is None
68         if problem_type not in self.__fits__:
69             raise TypeError("No learner to handle '{}'".format(problem_type))
70         if self.__learners[problem_type] is None:
71             learner = self.__fits__[problem_type](**self.__kwargs(problem_type))
72             learner.use_default_preprocessors = self.use_default_preprocessors
73             self.__learners[problem_type] = learner
74         return self.__learners[problem_type]
75 
76     def __kwargs(self, problem_type):
77         learner_kwargs = set(
78             self.__fits__[problem_type].__init__.__code__.co_varnames[1:])
79         changed_kwargs = self._change_kwargs(self.kwargs, problem_type)
80         return {k: v for k, v in changed_kwargs.items() if k in learner_kwargs}
81 
82     def _change_kwargs(self, kwargs, problem_type):
83         """Handle the kwargs to be passed to the learner before they are used.
84 
85         In some cases we need to manipulate the kwargs that will be passed to
86         the learner, e.g. SGD takes a `loss` parameter in both the regression
87         and classification learners, but the learner widget cannot
88         differentiate between these two, so it passes classification and
89         regression loss parameters individually. The appropriate one must be
90         renamed into `loss` before passed to the actual learner instance. This
91         is done here.
92 
93         """
94         return kwargs
95 
96     @property
97     def supports_weights(self):
98         """The fitter supports weights if both the classification and
99         regression learners support weights."""
100         return (
101             hasattr(self.get_learner(self.CLASSIFICATION), 'supports_weights')
102             and self.get_learner(self.CLASSIFICATION).supports_weights) and (
103             hasattr(self.get_learner(self.REGRESSION), 'supports_weights')
104             and self.get_learner(self.REGRESSION).supports_weights)
105 
106     @property
107     def params(self):
108         raise TypeError(
109             'A fitter does not have its own params. If you need to access '
110             'learner params, please use the `get_params` method.')
111 
112     def get_params(self, problem_type):
113         """Access the specific learner params of a given learner."""
114         return self.get_learner(problem_type).params
115
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/Orange/modelling/base.py b/Orange/modelling/base.py
--- a/Orange/modelling/base.py
+++ b/Orange/modelling/base.py
@@ -55,6 +55,12 @@
             X, Y, W = data.X, data.Y, data.W if data.has_weights() else None
             return learner.fit(X, Y, W)
 
+    def preprocess(self, data):
+        if data.domain.has_discrete_class:
+            return self.get_learner(self.CLASSIFICATION).preprocess(data)
+        else:
+            return self.get_learner(self.REGRESSION).preprocess(data)
+
     def get_learner(self, problem_type):
         """Get the learner for a given problem type.

{"golden_diff": "diff --git a/Orange/modelling/base.py b/Orange/modelling/base.py\n--- a/Orange/modelling/base.py\n+++ b/Orange/modelling/base.py\n@@ -55,6 +55,12 @@\n             X, Y, W = data.X, data.Y, data.W if data.has_weights() else None\n             return learner.fit(X, Y, W)\n \n+    def preprocess(self, data):\n+        if data.domain.has_discrete_class:\n+            return self.get_learner(self.CLASSIFICATION).preprocess(data)\n+        else:\n+            return self.get_learner(self.REGRESSION).preprocess(data)\n+\n     def get_learner(self, problem_type):\n         \"\"\"Get the learner for a given problem type.\n", "issue": "Fitter preprocessors\n<!--\r\nThis is an issue template. Please fill in the relevant details in the\r\nsections below.\r\n-->\r\n\r\n##### Orange version\r\n<!-- From menu _Help\u2192About\u2192Version_ or code `Orange.version.full_version` -->\r\n3.4.0\r\n\r\n##### Expected behavior\r\nLearners use preprocessors.\r\n\r\n\r\n##### Actual behavior\r\nLearners extending the Fitter base class do not use preprocessors.\r\n\r\n\r\n##### Steps to reproduce the behavior\r\nUse a learner on e.g. hearth_disease data set\r\n\r\n\r\n##### Additional info (worksheets, data, screenshots, ...)\r\n\r\n\r\n\n", "before_files": [{"content": "from Orange.base import Learner, Model\n\n\nclass FitterMeta(type):\n    \"\"\"Ensure that each subclass of the `Fitter` class overrides the `__fits__`\n    attribute with a valid value.\"\"\"\n    def __new__(mcs, name, bases, attrs):\n        # Check that a fitter implementation defines a valid `__fits__`\n        if any(cls.__name__ == 'Fitter' for cls in bases):\n            fits = attrs.get('__fits__')\n            assert isinstance(fits, dict), '__fits__ must be dict instance'\n            assert fits.get('classification') and fits.get('regression'), \\\n                ('`__fits__` property does not define classification '\n                 'or regression learner. Use a simple learner if you don\\'t '\n                 'need the functionality provided by Fitter.')\n        return super().__new__(mcs, name, bases, attrs)\n\n\nclass Fitter(Learner, metaclass=FitterMeta):\n    \"\"\"Handle multiple types of target variable with one learner.\n\n    Subclasses of this class serve as a sort of dispatcher. When subclassing,\n    we provide a `dict` which contain actual learner classes that handle\n    appropriate data types. The fitter can then be used on any data and will\n    delegate the work to the appropriate learner.\n\n    If the learners that handle each data type require different parameters,\n    you should pass in all the possible parameters to the fitter. The fitter\n    will then determine which parameters have to be passed to individual\n    learners.\n\n    \"\"\"\n    __fits__ = None\n    __returns__ = Model\n\n    # Constants to indicate what kind of problem we're dealing with\n    CLASSIFICATION, REGRESSION = 'classification', 'regression'\n\n    def __init__(self, preprocessors=None, **kwargs):\n        super().__init__(preprocessors=preprocessors)\n        self.kwargs = kwargs\n        # Make sure to pass preprocessor params to individual learners\n        self.kwargs['preprocessors'] = preprocessors\n        self.__learners = {self.CLASSIFICATION: None, self.REGRESSION: None}\n\n    def _fit_model(self, data):\n        if data.domain.has_discrete_class:\n            learner = self.get_learner(self.CLASSIFICATION)\n        else:\n            learner = self.get_learner(self.REGRESSION)\n\n        if type(self).fit is Learner.fit:\n            return learner.fit_storage(data)\n        else:\n            X, Y, W = data.X, data.Y, data.W if data.has_weights() else None\n            return learner.fit(X, Y, W)\n\n    def get_learner(self, problem_type):\n        \"\"\"Get the learner for a given problem type.\n\n        Returns\n        -------\n        Learner\n            The appropriate learner for the given problem type.\n\n        \"\"\"\n        # Prevent trying to access the learner when problem type is None\n        if problem_type not in self.__fits__:\n            raise TypeError(\"No learner to handle '{}'\".format(problem_type))\n        if self.__learners[problem_type] is None:\n            learner = self.__fits__[problem_type](**self.__kwargs(problem_type))\n            learner.use_default_preprocessors = self.use_default_preprocessors\n            self.__learners[problem_type] = learner\n        return self.__learners[problem_type]\n\n    def __kwargs(self, problem_type):\n        learner_kwargs = set(\n            self.__fits__[problem_type].__init__.__code__.co_varnames[1:])\n        changed_kwargs = self._change_kwargs(self.kwargs, problem_type)\n        return {k: v for k, v in changed_kwargs.items() if k in learner_kwargs}\n\n    def _change_kwargs(self, kwargs, problem_type):\n        \"\"\"Handle the kwargs to be passed to the learner before they are used.\n\n        In some cases we need to manipulate the kwargs that will be passed to\n        the learner, e.g. SGD takes a `loss` parameter in both the regression\n        and classification learners, but the learner widget cannot\n        differentiate between these two, so it passes classification and\n        regression loss parameters individually. The appropriate one must be\n        renamed into `loss` before passed to the actual learner instance. This\n        is done here.\n\n        \"\"\"\n        return kwargs\n\n    @property\n    def supports_weights(self):\n        \"\"\"The fitter supports weights if both the classification and\n        regression learners support weights.\"\"\"\n        return (\n            hasattr(self.get_learner(self.CLASSIFICATION), 'supports_weights')\n            and self.get_learner(self.CLASSIFICATION).supports_weights) and (\n            hasattr(self.get_learner(self.REGRESSION), 'supports_weights')\n            and self.get_learner(self.REGRESSION).supports_weights)\n\n    @property\n    def params(self):\n        raise TypeError(\n            'A fitter does not have its own params. If you need to access '\n            'learner params, please use the `get_params` method.')\n\n    def get_params(self, problem_type):\n        \"\"\"Access the specific learner params of a given learner.\"\"\"\n        return self.get_learner(problem_type).params\n", "path": "Orange/modelling/base.py"}], "after_files": [{"content": "from Orange.base import Learner, Model\n\n\nclass FitterMeta(type):\n    \"\"\"Ensure that each subclass of the `Fitter` class overrides the `__fits__`\n    attribute with a valid value.\"\"\"\n    def __new__(mcs, name, bases, attrs):\n        # Check that a fitter implementation defines a valid `__fits__`\n        if any(cls.__name__ == 'Fitter' for cls in bases):\n            fits = attrs.get('__fits__')\n            assert isinstance(fits, dict), '__fits__ must be dict instance'\n            assert fits.get('classification') and fits.get('regression'), \\\n                ('`__fits__` property does not define classification '\n                 'or regression learner. Use a simple learner if you don\\'t '\n                 'need the functionality provided by Fitter.')\n        return super().__new__(mcs, name, bases, attrs)\n\n\nclass Fitter(Learner, metaclass=FitterMeta):\n    \"\"\"Handle multiple types of target variable with one learner.\n\n    Subclasses of this class serve as a sort of dispatcher. When subclassing,\n    we provide a `dict` which contain actual learner classes that handle\n    appropriate data types. The fitter can then be used on any data and will\n    delegate the work to the appropriate learner.\n\n    If the learners that handle each data type require different parameters,\n    you should pass in all the possible parameters to the fitter. The fitter\n    will then determine which parameters have to be passed to individual\n    learners.\n\n    \"\"\"\n    __fits__ = None\n    __returns__ = Model\n\n    # Constants to indicate what kind of problem we're dealing with\n    CLASSIFICATION, REGRESSION = 'classification', 'regression'\n\n    def __init__(self, preprocessors=None, **kwargs):\n        super().__init__(preprocessors=preprocessors)\n        self.kwargs = kwargs\n        # Make sure to pass preprocessor params to individual learners\n        self.kwargs['preprocessors'] = preprocessors\n        self.__learners = {self.CLASSIFICATION: None, self.REGRESSION: None}\n\n    def _fit_model(self, data):\n        if data.domain.has_discrete_class:\n            learner = self.get_learner(self.CLASSIFICATION)\n        else:\n            learner = self.get_learner(self.REGRESSION)\n\n        if type(self).fit is Learner.fit:\n            return learner.fit_storage(data)\n        else:\n            X, Y, W = data.X, data.Y, data.W if data.has_weights() else None\n            return learner.fit(X, Y, W)\n\n    def preprocess(self, data):\n        if data.domain.has_discrete_class:\n            return self.get_learner(self.CLASSIFICATION).preprocess(data)\n        else:\n            return self.get_learner(self.REGRESSION).preprocess(data)\n\n    def get_learner(self, problem_type):\n        \"\"\"Get the learner for a given problem type.\n\n        Returns\n        -------\n        Learner\n            The appropriate learner for the given problem type.\n\n        \"\"\"\n        # Prevent trying to access the learner when problem type is None\n        if problem_type not in self.__fits__:\n            raise TypeError(\"No learner to handle '{}'\".format(problem_type))\n        if self.__learners[problem_type] is None:\n            learner = self.__fits__[problem_type](**self.__kwargs(problem_type))\n            learner.use_default_preprocessors = self.use_default_preprocessors\n            self.__learners[problem_type] = learner\n        return self.__learners[problem_type]\n\n    def __kwargs(self, problem_type):\n        learner_kwargs = set(\n            self.__fits__[problem_type].__init__.__code__.co_varnames[1:])\n        changed_kwargs = self._change_kwargs(self.kwargs, problem_type)\n        return {k: v for k, v in changed_kwargs.items() if k in learner_kwargs}\n\n    def _change_kwargs(self, kwargs, problem_type):\n        \"\"\"Handle the kwargs to be passed to the learner before they are used.\n\n        In some cases we need to manipulate the kwargs that will be passed to\n        the learner, e.g. SGD takes a `loss` parameter in both the regression\n        and classification learners, but the learner widget cannot\n        differentiate between these two, so it passes classification and\n        regression loss parameters individually. The appropriate one must be\n        renamed into `loss` before passed to the actual learner instance. This\n        is done here.\n\n        \"\"\"\n        return kwargs\n\n    @property\n    def supports_weights(self):\n        \"\"\"The fitter supports weights if both the classification and\n        regression learners support weights.\"\"\"\n        return (\n            hasattr(self.get_learner(self.CLASSIFICATION), 'supports_weights')\n            and self.get_learner(self.CLASSIFICATION).supports_weights) and (\n            hasattr(self.get_learner(self.REGRESSION), 'supports_weights')\n            and self.get_learner(self.REGRESSION).supports_weights)\n\n    @property\n    def params(self):\n        raise TypeError(\n            'A fitter does not have its own params. If you need to access '\n            'learner params, please use the `get_params` method.')\n\n    def get_params(self, problem_type):\n        \"\"\"Access the specific learner params of a given learner.\"\"\"\n        return self.get_learner(problem_type).params\n", "path": "Orange/modelling/base.py"}]}