Repository: mindsdb/mindsdb

Issue:
Rename functionality to the predictor interface
Add a `rename` function to the predictor interface, it should take two arguments: `new_name` and `current_name`

`current_name` should default to `self.name` if not provided.

This required changing the model name in a few places through the light and heavy metadata as well as modifying the underlying metadata and learning backend files.



Discussion:
Why the ```current_name``` argument is needed in a ```rename``` function?
In case you fish to rename a predictor other than the one being used right now... it could also be designed to just have a single argument and take the current name to always be `self.name`. 

The design with two arguments was so because at some point I wanted to make all these 'administrative' functions separate from the predictor, and thus I didn't want to have them rely on `self`... but, it can be made other way, it doesn't matter much.
Looking into adding this. I don't quite understand the logic behind having a class method for renaming other instances of that class, could you provide some context on why we might want to do this? 
True, it makes little sense, it should just be `@staticmethod`, and I'm fine with it being written as `@staticmethod`.

If you want to know the reasoning for initially not wanting to make it static it was as follows (quite boring and long):

1. * There are a bunch on standalone method define on the predictor already as normal methods (not static) which should really be static (e.g. list all model, load the data for a specific model, turn the model metadata into a json print-friendly format... etc)

2. * These methods were not made static because they require one or two internal variable, which could themselves be created by static functions, but they aren't, they require 1 or 2 lines in the predictor initialization to compute

3. *  These methods are use in some other places (mindsb_server) on an instance of the predictor, rather than as static methods.

So to migrate all methods that fall into (1) to static I would have to fix (2) and (3)... which is annoying, not hard, but annoying, loads of places to change stuff in, I want to get to it at some point, probably next week.

Until then, I though it would be more consistent to keep all these stand-alone functionality as methods, just for the sake of interface consistency. But I can live with it being static as well, either way works, it would end up as a separate function in the future anyway :)


@George3d6 is this taken care of? If not then I can work on it
Still needs doing, yes
thanks @George3d6 I will look into it over the Weekend 
@mohsinkhansymc 
Since no PR has come in I assume you didn't have the time to look into it. Thanks for wanting to help, but as it turns out this feature just became rather critical to implement, so I will be doing it myself in the next PR, so please don't start work on it as it will likely be implemented by the time you finish.
 

Resolving patch:
diff --git a/mindsdb/__about__.py b/mindsdb/__about__.py
--- a/mindsdb/__about__.py
+++ b/mindsdb/__about__.py
@@ -1,6 +1,6 @@
 __title__ = 'MindsDB'
 __package_name__ = 'mindsdb'
-__version__ = '1.7.0'
+__version__ = '1.7.1'
 __description__ = "MindsDB's goal is to make it very simple for developers to use the power of artificial neural networks in their projects."
 __email__ = "jorge@mindsdb.com"
 __author__ = 'MindsDB Inc'
diff --git a/mindsdb/libs/controllers/predictor.py b/mindsdb/libs/controllers/predictor.py
--- a/mindsdb/libs/controllers/predictor.py
+++ b/mindsdb/libs/controllers/predictor.py
@@ -4,6 +4,7 @@
 import uuid
 import traceback
 import pickle
+import shutil
 
 from mindsdb.libs.data_types.mindsdb_logger import MindsdbLogger
 from mindsdb.libs.helpers.multi_data_source import getDS
@@ -404,6 +405,69 @@ def load_model(self, model_archive_path=None):
         """
         self.load(model_archive_path)
 
+    def rename_model(self, old_model_name, new_model_name):
+        """
+        If you want to export a model to a file
+
+        :param old_model_name: this is the name of the model you wish to rename
+        :param new_model_name: this is the new name of the model
+        :return: bool (True/False) True if mind was exported successfully
+        """
+
+        if old_model_name == new_model_name:
+            return True
+
+        moved_a_backend = False
+        for extension in ['_lightwood_data', '_ludwig_data']:
+            try:
+                shutil.move(os.path.join(CONFIG.MINDSDB_STORAGE_PATH, old_model_name + extension), os.path.join(CONFIG.MINDSDB_STORAGE_PATH, new_model_name + extension))
+                moved_a_backend = True
+            except:
+                pass
+
+        if not moved_a_backend:
+            return False
+
+        with open(os.path.join(CONFIG.MINDSDB_STORAGE_PATH, old_model_name + '_light_model_metadata.pickle'), 'rb') as fp:
+            lmd =pickle.load(fp)
+
+        with open(os.path.join(CONFIG.MINDSDB_STORAGE_PATH, old_model_name + '_heavy_model_metadata.pickle'), 'rb') as fp:
+            hmd =pickle.load(fp)
+
+        lmd['name'] = new_model_name
+        hmd['name'] = new_model_name
+
+        renamed_one_backend = False
+        try:
+            lmd['ludwig_data']['ludwig_save_path'] = lmd['ludwig_data']['ludwig_save_path'].replace(old_model_name, new_model_name)
+            renamed_one_backend = True
+        except:
+            pass
+
+        try:
+            lmd['lightwood_data']['save_path'] = lmd['lightwood_data']['save_path'].replace(old_model_name, new_model_name)
+            renamed_one_backend = True
+        except:
+            pass
+
+        if not renamed_one_backend:
+            return False
+
+        with open(os.path.join(CONFIG.MINDSDB_STORAGE_PATH, new_model_name + '_light_model_metadata.pickle'), 'wb') as fp:
+            pickle.dump(lmd, fp,protocol=pickle.HIGHEST_PROTOCOL)
+
+        with open(os.path.join(CONFIG.MINDSDB_STORAGE_PATH, new_model_name + '_heavy_model_metadata.pickle'), 'wb') as fp:
+            pickle.dump(hmd, fp,protocol=pickle.HIGHEST_PROTOCOL)
+
+
+
+
+
+        os.remove(os.path.join(CONFIG.MINDSDB_STORAGE_PATH, old_model_name + '_light_model_metadata.pickle'))
+        os.remove(os.path.join(CONFIG.MINDSDB_STORAGE_PATH, old_model_name + '_heavy_model_metadata.pickle'))
+        return True
+
+
     def delete_model(self, model_name):
         """
         If you want to export a model to a file
diff --git a/mindsdb/libs/controllers/transaction.py b/mindsdb/libs/controllers/transaction.py
--- a/mindsdb/libs/controllers/transaction.py
+++ b/mindsdb/libs/controllers/transaction.py
@@ -270,9 +270,6 @@ def run(self):
             return
 
         if self.lmd['type'] == TRANSACTION_LEARN:
-            self.output_data.data_frame = [['Model ' + self.lmd['name'] + ' training.']]
-            self.output_data.columns = ['Status']
-
             if CONFIG.EXEC_LEARN_IN_THREAD == False:
                 self._execute_learn()
             else:


Test patch:
diff --git a/tests/ci_tests/tests.py b/tests/ci_tests/tests.py
--- a/tests/ci_tests/tests.py
+++ b/tests/ci_tests/tests.py
@@ -13,7 +13,12 @@ def basic_test(backend='ludwig',use_gpu=True,ignore_columns=[], run_extra=False)
     mdb.learn(to_predict='rental_price',from_data="https://s3.eu-west-2.amazonaws.com/mindsdb-example-data/home_rentals.csv",backend=backend, stop_training_in_x_seconds=20,use_gpu=use_gpu)
 
     # Reload & Predict
-    mdb = Predictor(name='home_rentals_price')
+    model_name = 'home_rentals_price'
+    if run_extra:
+        mdb.rename_model('home_rentals_price', 'home_rentals_price_renamed')
+        model_name = 'home_rentals_price_renamed'
+
+    mdb = Predictor(name=model_name)
     prediction = mdb.predict(when={'sqft':300}, use_gpu=use_gpu)
 
     # Test all different forms of output
@@ -41,6 +46,6 @@ def basic_test(backend='ludwig',use_gpu=True,ignore_columns=[], run_extra=False)
     print('\n\n')
 
     # See if we can get the adapted metadata
-    amd = mdb.get_model_data('home_rentals_price')
+    amd = mdb.get_model_data(model_name)
     # Make some simple assertions about it
     assert(5 < len(list(amd.keys())))