Repository: oppia/oppia

Issue:
Remove version attribute in ModifiableUserData class
The `version` attribute in core.domain.user_domain.ModifiableUserData class needs to be removed because we already have the `schema_version` maintained. Ref to [this](https://github.com/oppia/oppia/pull/10241/files#r491717786) thread for more context.


Discussion:

Resolving patch:
diff --git a/core/domain/user_domain.py b/core/domain/user_domain.py
--- a/core/domain/user_domain.py
+++ b/core/domain/user_domain.py
@@ -383,7 +383,7 @@ class ModifiableUserData(python_utils.OBJECT):
     def __init__(
             self, display_alias, pin, preferred_language_codes,
             preferred_site_language_code, preferred_audio_language_code,
-            version, user_id=None):
+            user_id=None):
         """Constructs a ModifiableUserData domain object.
 
         Args:
@@ -396,7 +396,6 @@ def __init__(
                 preference.
             preferred_audio_language_code: str or None. Audio language
                 preference.
-            version: int. The version of modifiable user data schema.
             user_id: str or None. User ID of the user whose data is being
                 updated. None if request did not have a user_id for the user
                 yet and expects the backend to create a new user entry for it.
@@ -409,7 +408,6 @@ def __init__(
         # The user_id is not intended to be a modifiable attribute, it is just
         # needed to identify the object.
         self.user_id = user_id
-        self.version = version
 
     @classmethod
     def from_dict(cls, modifiable_user_data_dict):
@@ -429,7 +427,6 @@ def from_dict(cls, modifiable_user_data_dict):
             modifiable_user_data_dict['preferred_language_codes'],
             modifiable_user_data_dict['preferred_site_language_code'],
             modifiable_user_data_dict['preferred_audio_language_code'],
-            modifiable_user_data_dict['schema_version'],
             modifiable_user_data_dict['user_id'],
         )
 
@@ -448,4 +445,17 @@ def from_raw_dict(cls, raw_user_data_dict):
             ModifiableUserData. The domain object representing the user data
             dict transformed according to the latest schema version.
         """
-        return ModifiableUserData.from_dict(raw_user_data_dict)
+        data_schema_version = raw_user_data_dict.get('schema_version')
+
+        if data_schema_version is None:
+            raise Exception(
+                'Invalid modifiable user data: no schema version specified.')
+        if (data_schema_version < 1) or (
+                data_schema_version > cls.CURRENT_SCHEMA_VERSION):
+            raise Exception(
+                'Invalid version %s received. At present we can only process v1'
+                ' to v%s modifiable user data.' % (
+                    data_schema_version, cls.CURRENT_SCHEMA_VERSION)
+            )
+
+        return cls.from_dict(raw_user_data_dict)


Test patch:
diff --git a/core/domain/user_domain_test.py b/core/domain/user_domain_test.py
--- a/core/domain/user_domain_test.py
+++ b/core/domain/user_domain_test.py
@@ -37,11 +37,11 @@ class to create a new version of the schema for testing migration of old
     def __init__(
             self, display_alias, pin, preferred_language_codes,
             preferred_site_language_code, preferred_audio_language_code,
-            version, user_id=None, fake_field=None):
+            user_id=None, fake_field=None):
         super(MockModifiableUserData, self).__init__(
             display_alias, pin, preferred_language_codes,
             preferred_site_language_code, preferred_audio_language_code,
-            version, user_id=None)
+            user_id=None)
         self.fake_field = fake_field
 
     CURRENT_SCHEMA_VERSION = 2
@@ -55,7 +55,6 @@ def from_dict(cls, modifiable_user_data_dict):
             modifiable_user_data_dict['preferred_language_codes'],
             modifiable_user_data_dict['preferred_site_language_code'],
             modifiable_user_data_dict['preferred_audio_language_code'],
-            modifiable_user_data_dict['schema_version'],
             modifiable_user_data_dict['user_id'],
             modifiable_user_data_dict['fake_field']
         )
@@ -72,12 +71,12 @@ def _convert_v1_dict_to_v2_dict(cls, user_data_dict):
     # then returning a ModifiableUserData domain object.
     @classmethod
     def from_raw_dict(cls, raw_user_data_dict):
-        intial_schema_version = raw_user_data_dict['schema_version']
-        data_schema_version = intial_schema_version
+        data_schema_version = raw_user_data_dict.get('schema_version')
         user_data_dict = raw_user_data_dict
 
         if data_schema_version == 1:
             user_data_dict = cls._convert_v1_dict_to_v2_dict(user_data_dict)
+            data_schema_version = 2
 
         return MockModifiableUserData.from_dict(user_data_dict)
 
@@ -583,7 +582,6 @@ class ModifiableUserDataTests(test_utils.GenericTestBase):
 
     def test_initialization_with_none_user_id_is_successful(self):
         """Testing init method user id set None."""
-        schema_version = 1
         user_data_dict = {
             'schema_version': 1,
             'display_alias': 'display_alias',
@@ -613,11 +611,9 @@ def test_initialization_with_none_user_id_is_successful(self):
             'preferred_audio_language_code'
         )
         self.assertIsNone(modifiable_user_data.user_id)
-        self.assertEqual(modifiable_user_data.version, schema_version)
 
     def test_initialization_with_valid_user_id_is_successful(self):
         """Testing init method with a valid user id set."""
-        schema_version = 1
         user_data_dict = {
             'schema_version': 1,
             'display_alias': 'display_alias',
@@ -647,7 +643,40 @@ def test_initialization_with_valid_user_id_is_successful(self):
             'preferred_audio_language_code'
         )
         self.assertEqual(modifiable_user_data.user_id, 'user_id')
-        self.assertEqual(modifiable_user_data.version, schema_version)
+
+    def test_from_raw_dict_with_none_schema_version_raises_error(self):
+        user_data_dict = {
+            'schema_version': None,
+            'display_alias': 'display_alias',
+            'pin': '123',
+            'preferred_language_codes': 'preferred_language_codes',
+            'preferred_site_language_code': 'preferred_site_language_code',
+            'preferred_audio_language_code': 'preferred_audio_language_code',
+            'user_id': 'user_id',
+        }
+        error_msg = 'Invalid modifiable user data: no schema version specified.'
+        with self.assertRaisesRegexp(Exception, error_msg):
+            user_domain.ModifiableUserData.from_raw_dict(user_data_dict)
+
+    def test_from_raw_dict_with_invalid_schema_version_raises_error(self):
+        user_data_dict = {
+            'schema_version': 1,
+            'display_alias': 'display_alias',
+            'pin': '123',
+            'preferred_language_codes': 'preferred_language_codes',
+            'preferred_site_language_code': 'preferred_site_language_code',
+            'preferred_audio_language_code': 'preferred_audio_language_code',
+            'user_id': 'user_id',
+        }
+        invalid_schema_versions = [
+            -1, 0, user_domain.ModifiableUserData.CURRENT_SCHEMA_VERSION + 1,
+            '', 'abc', '-1', '1'
+        ]
+        for version in invalid_schema_versions:
+            error_msg = 'Invalid version %s received.' % version
+            user_data_dict['schema_version'] = version
+            with self.assertRaisesRegexp(Exception, error_msg):
+                user_domain.ModifiableUserData.from_raw_dict(user_data_dict)
 
     # This test should be modified to use the original class ModifiableUserData
     # itself when the CURRENT_SCHEMA_VERSION has been updated to 2 or higher.
@@ -674,7 +703,6 @@ def test_mock_modifiable_user_data_class_with_all_attributes_given(self):
             modifiable_user_data.preferred_audio_language_code, 'en')
         self.assertEqual(modifiable_user_data.fake_field, 'set_value')
         self.assertEqual(modifiable_user_data.user_id, None)
-        self.assertEqual(modifiable_user_data.version, 2)
 
     # This test should be modified to use the original class ModifiableUserData
     # itself when the CURRENT_SCHEMA_VERSION has been updated to 2 or higher.
@@ -688,8 +716,8 @@ def test_mock_migration_from_old_version_to_new_works_correctly(self):
             'preferred_audio_language_code': 'en',
             'user_id': None
         }
-        modifiable_user_data = MockModifiableUserData.from_raw_dict(
-            user_data_dict)
+        modifiable_user_data = (
+            MockModifiableUserData.from_raw_dict(user_data_dict))
         self.assertEqual(modifiable_user_data.display_alias, 'name')
         self.assertEqual(modifiable_user_data.pin, '123')
         self.assertEqual(
@@ -700,4 +728,3 @@ def test_mock_migration_from_old_version_to_new_works_correctly(self):
             modifiable_user_data.preferred_audio_language_code, 'en')
         self.assertEqual(modifiable_user_data.fake_field, 'default_value')
         self.assertEqual(modifiable_user_data.user_id, None)
-        self.assertEqual(modifiable_user_data.version, 2)
diff --git a/core/domain/user_services_test.py b/core/domain/user_services_test.py
--- a/core/domain/user_services_test.py
+++ b/core/domain/user_services_test.py
@@ -70,15 +70,28 @@ class UserServicesUnitTests(test_utils.GenericTestBase):
 
     def setUp(self):
         super(UserServicesUnitTests, self).setUp()
-        schema_version = 1
-        self.modifiable_user_data = user_domain.ModifiableUserData(
-            'display_alias', '12345', [constants.DEFAULT_LANGUAGE_CODE],
-            None, None, schema_version, 'user_id'
-        )
-        self.modifiable_new_user_data = user_domain.ModifiableUserData(
-            'display_alias3', '12345', [constants.DEFAULT_LANGUAGE_CODE],
-            None, None, schema_version
-        )
+        user_data_dict = {
+            'schema_version': 1,
+            'display_alias': 'display_alias',
+            'pin': '12345',
+            'preferred_language_codes': [constants.DEFAULT_LANGUAGE_CODE],
+            'preferred_site_language_code': None,
+            'preferred_audio_language_code': None,
+            'user_id': 'user_id',
+        }
+        new_user_data_dict = {
+            'schema_version': 1,
+            'display_alias': 'display_alias3',
+            'pin': '12345',
+            'preferred_language_codes': [constants.DEFAULT_LANGUAGE_CODE],
+            'preferred_site_language_code': None,
+            'preferred_audio_language_code': None,
+            'user_id': None,
+        }
+        self.modifiable_user_data = (
+            user_domain.ModifiableUserData.from_raw_dict(user_data_dict))
+        self.modifiable_new_user_data = (
+            user_domain.ModifiableUserData.from_raw_dict(new_user_data_dict))
 
     def test_is_user_id_valid(self):
         self.assertTrue(
@@ -779,7 +792,6 @@ def test_create_new_profile_with_parent_user_pin_not_set_raises_error(self):
                 gae_id, email, [self.modifiable_new_user_data])
 
     def test_create_multiple_new_profiles_for_same_user_works_correctly(self):
-        schema_version = 1
         gae_id = 'gae_id'
         email = 'new@example.com'
         display_alias = 'display_alias'
@@ -799,10 +811,18 @@ def test_create_multiple_new_profiles_for_same_user_works_correctly(self):
         user_services.update_multiple_users_data([self.modifiable_user_data])
         self.modifiable_new_user_data.display_alias = display_alias_2
         self.modifiable_new_user_data.pin = profile_pin
-        modifiable_new_user_data_2 = user_domain.ModifiableUserData(
-            display_alias_3, None, [constants.DEFAULT_LANGUAGE_CODE],
-            None, None, schema_version
-        )
+        new_user_data_dict_2 = {
+            'schema_version': 1,
+            'display_alias': display_alias_3,
+            'pin': None,
+            'preferred_language_codes': [constants.DEFAULT_LANGUAGE_CODE],
+            'preferred_site_language_code': None,
+            'preferred_audio_language_code': None,
+            'user_id': None,
+        }
+        modifiable_new_user_data_2 = (
+            user_domain.ModifiableUserData.from_raw_dict(
+                new_user_data_dict_2))
         user_settings_list = user_services.create_new_profiles(
             gae_id, email, [
                 self.modifiable_new_user_data, modifiable_new_user_data_2
@@ -937,7 +957,6 @@ def test_update_users_for_user_with_non_existent_id_raises_error(self):
 
     def test_update_users_data_for_multiple_users_works_correctly(self):
         # Preparing for the test.
-        schema_version = 1
         gae_id = 'gae_id'
         email = 'new@example.com'
         display_alias = 'display_alias'
@@ -957,10 +976,18 @@ def test_update_users_data_for_multiple_users_works_correctly(self):
         user_services.update_multiple_users_data([self.modifiable_user_data])
         self.modifiable_new_user_data.display_alias = display_alias_2
         self.modifiable_new_user_data.pin = profile_pin
-        modifiable_new_user_data_2 = user_domain.ModifiableUserData(
-            display_alias_3, None, [constants.DEFAULT_LANGUAGE_CODE],
-            None, None, schema_version
-        )
+        new_user_data_dict_2 = {
+            'schema_version': 1,
+            'display_alias': display_alias_3,
+            'pin': None,
+            'preferred_language_codes': [constants.DEFAULT_LANGUAGE_CODE],
+            'preferred_site_language_code': None,
+            'preferred_audio_language_code': None,
+            'user_id': None,
+        }
+        modifiable_new_user_data_2 = (
+            user_domain.ModifiableUserData.from_raw_dict(
+                new_user_data_dict_2))
         user_settings_list = user_services.create_new_profiles(
             gae_id, email, [
                 self.modifiable_new_user_data, modifiable_new_user_data_2
@@ -1766,15 +1793,28 @@ def setUp(self):
         self.user_settings = user_services.get_user_settings(self.owner_id)
         self.user_settings.validate()
         self.assertEqual(self.owner.role, feconf.ROLE_ID_EXPLORATION_EDITOR)
-        schema_version = 1
-        self.modifiable_user_data = user_domain.ModifiableUserData(
-            'display_alias', '12345', [constants.DEFAULT_LANGUAGE_CODE],
-            None, None, schema_version, 'user_id'
-        )
-        self.modifiable_new_user_data = user_domain.ModifiableUserData(
-            'display_alias3', '12345', [constants.DEFAULT_LANGUAGE_CODE],
-            None, None, schema_version
-        )
+        user_data_dict = {
+            'schema_version': 1,
+            'display_alias': 'display_alias',
+            'pin': '12345',
+            'preferred_language_codes': [constants.DEFAULT_LANGUAGE_CODE],
+            'preferred_site_language_code': None,
+            'preferred_audio_language_code': None,
+            'user_id': 'user_id',
+        }
+        self.modifiable_user_data = (
+            user_domain.ModifiableUserData.from_raw_dict(user_data_dict))
+        new_user_data_dict = {
+            'schema_version': 1,
+            'display_alias': 'display_alias_3',
+            'pin': None,
+            'preferred_language_codes': [constants.DEFAULT_LANGUAGE_CODE],
+            'preferred_site_language_code': None,
+            'preferred_audio_language_code': None,
+            'user_id': None,
+        }
+        self.modifiable_new_user_data = (
+            user_domain.ModifiableUserData.from_raw_dict(new_user_data_dict))
 
     def test_validate_non_str_user_id_raises_exception(self):
         self.user_settings.user_id = 0
diff --git a/core/domain/wipeout_service_test.py b/core/domain/wipeout_service_test.py
--- a/core/domain/wipeout_service_test.py
+++ b/core/domain/wipeout_service_test.py
@@ -231,15 +231,28 @@ def setUp(self):
         self.signup(self.USER_2_EMAIL, self.USER_2_USERNAME)
         self.user_2_id = self.get_user_id_from_email(self.USER_2_EMAIL)
         self.user_1_gae_id = self.get_gae_id_from_email(self.USER_1_EMAIL)
-        schema_version = 1
-        self.modifiable_user_data = user_domain.ModifiableUserData(
-            'display_alias', '12345', [constants.DEFAULT_LANGUAGE_CODE],
-            None, None, schema_version, self.user_1_id
-        )
-        self.modifiable_new_user_data = user_domain.ModifiableUserData(
-            'display_alias3', '12345', [constants.DEFAULT_LANGUAGE_CODE],
-            None, None, schema_version
-        )
+        user_data_dict = {
+            'schema_version': 1,
+            'display_alias': 'display_alias',
+            'pin': '12345',
+            'preferred_language_codes': [constants.DEFAULT_LANGUAGE_CODE],
+            'preferred_site_language_code': None,
+            'preferred_audio_language_code': None,
+            'user_id': self.user_1_id,
+        }
+        new_user_data_dict = {
+            'schema_version': 1,
+            'display_alias': 'display_alias3',
+            'pin': '12345',
+            'preferred_language_codes': [constants.DEFAULT_LANGUAGE_CODE],
+            'preferred_site_language_code': None,
+            'preferred_audio_language_code': None,
+            'user_id': None,
+        }
+        self.modifiable_user_data = (
+            user_domain.ModifiableUserData.from_raw_dict(user_data_dict))
+        self.modifiable_new_user_data = (
+            user_domain.ModifiableUserData.from_raw_dict(new_user_data_dict))
 
         user_services.update_multiple_users_data(
             [self.modifiable_user_data])
@@ -3796,16 +3809,29 @@ def setUp(self):
         user_models.LearnerPlaylistModel(
             id=self.user_2_id, exploration_ids=[], collection_ids=[]
         ).put()
-        schema_version = 1
         self.user_1_gae_id = self.get_gae_id_from_email(self.USER_1_EMAIL)
-        self.modifiable_user_data = user_domain.ModifiableUserData(
-            'display_alias', '12345', [constants.DEFAULT_LANGUAGE_CODE],
-            None, None, schema_version, self.user_1_id
-        )
-        self.modifiable_new_user_data = user_domain.ModifiableUserData(
-            'display_alias3', '12345', [constants.DEFAULT_LANGUAGE_CODE],
-            None, None, schema_version
-        )
+        user_data_dict = {
+            'schema_version': 1,
+            'display_alias': 'display_alias',
+            'pin': '12345',
+            'preferred_language_codes': [constants.DEFAULT_LANGUAGE_CODE],
+            'preferred_site_language_code': None,
+            'preferred_audio_language_code': None,
+            'user_id': self.user_1_id,
+        }
+        new_user_data_dict = {
+            'schema_version': 1,
+            'display_alias': 'display_alias3',
+            'pin': '12345',
+            'preferred_language_codes': [constants.DEFAULT_LANGUAGE_CODE],
+            'preferred_site_language_code': None,
+            'preferred_audio_language_code': None,
+            'user_id': None,
+        }
+        self.modifiable_user_data = (
+            user_domain.ModifiableUserData.from_raw_dict(user_data_dict))
+        self.modifiable_new_user_data = (
+            user_domain.ModifiableUserData.from_raw_dict(new_user_data_dict))
 
         user_services.update_multiple_users_data(
             [self.modifiable_user_data])
@@ -4078,15 +4104,28 @@ def setUp(self):
         self.user_1_id = self.get_user_id_from_email(self.USER_1_EMAIL)
         self.user_2_id = self.get_user_id_from_email(self.USER_2_EMAIL)
         self.user_1_gae_id = self.get_gae_id_from_email(self.USER_1_EMAIL)
-        schema_version = 1
-        self.modifiable_user_data = user_domain.ModifiableUserData(
-            'display_alias', '12345', [constants.DEFAULT_LANGUAGE_CODE],
-            None, None, schema_version, self.user_1_id
-        )
-        self.modifiable_new_user_data = user_domain.ModifiableUserData(
-            'display_alias3', '12345', [constants.DEFAULT_LANGUAGE_CODE],
-            None, None, schema_version
-        )
+        user_data_dict = {
+            'schema_version': 1,
+            'display_alias': 'display_alias',
+            'pin': '12345',
+            'preferred_language_codes': [constants.DEFAULT_LANGUAGE_CODE],
+            'preferred_site_language_code': None,
+            'preferred_audio_language_code': None,
+            'user_id': self.user_1_id,
+        }
+        new_user_data_dict = {
+            'schema_version': 1,
+            'display_alias': 'display_alias3',
+            'pin': '12345',
+            'preferred_language_codes': [constants.DEFAULT_LANGUAGE_CODE],
+            'preferred_site_language_code': None,
+            'preferred_audio_language_code': None,
+            'user_id': None,
+        }
+        self.modifiable_user_data = (
+            user_domain.ModifiableUserData.from_raw_dict(user_data_dict))
+        self.modifiable_new_user_data = (
+            user_domain.ModifiableUserData.from_raw_dict(new_user_data_dict))
 
         user_services.update_multiple_users_data(
             [self.modifiable_user_data])