Repository: oppia/oppia

Issue:
Fix firebase_admin import error when running scripts.start for the first time
Raised in https://github.com/oppia/oppia/discussions/16855#discussioncomment-4633743.

When installing Oppia, the following error is raised when scripts.start is run for the first time:

```
ModuleNotFoundError: No module named 'firebase_admin'
```

While there's an easy fix (run scripts.start again), this is annoying for new contributors.

The problem is probably with how we set up PYTHONPATH.
Fix firebase_admin import error when running scripts.start for the first time
Raised in https://github.com/oppia/oppia/discussions/16855#discussioncomment-4633743.

When installing Oppia, the following error is raised when scripts.start is run for the first time:

```
ModuleNotFoundError: No module named 'firebase_admin'
```

While there's an easy fix (run scripts.start again), this is annoying for new contributors.

The problem is probably with how we set up PYTHONPATH.


Discussion:
Interesting @Nik-09 how do you plan to proceed on this one ?

Hi, @legendarykamal I am yet to look at this issue completely.
I've run into this issue, as well. Should we be updating the onboarding documentation to include the suggested workaround from https://github.com/oppia/oppia/discussions/16855#discussioncomment-4633743?

/cc @seanlip since this relates to onboarding.
I ve run into this issue too. I think I downloaded firebase-admin using pip. It worked for me.
@462702985 will be working on this
I will check this issue this week.
I tried to modify system path and import directly from path but still not working. Any suggestion for what else I should try?
https://docs.google.com/document/d/1RydAE_CVcZdJ2JYpRsm_ltcg9IDOtrvjVfO_sRwVu3U/edit?usp=sharing

Hi @462702985 -- some questions:

(a) Are you able to reproduce the issue stated?
(b) If so, then when the installation fails, what is the PYTHONPATH, and does it include the path to firebase_admin?
(c) If other libs don't encounter this issue, then what is the difference between how those libs are installed and imported, vs firebase_admin? That's the main thing to investigate.

Basically, try to understand and construct a clear theory / mental model for _why_ the current issue is happening. Once you understand that fully, the fix should be easier to make.



> Hi @462702985 -- some questions:
> 
> (a) Are you able to reproduce the issue stated? (b) If so, then when the installation fails, what is the PYTHONPATH, and does it include the path to firebase_admin? (c) If other libs don't encounter this issue, then what is the difference between how those libs are installed and imported, vs firebase_admin? That's the main thing to investigate.
> 
> Basically, try to understand and construct a clear theory / mental model for _why_ the current issue is happening. Once you understand that fully, the fix should be easier to make.

I reproduced this issue and PYTHONPATH do not include path to firebase_admin. Other libs installed under pyenv folder(which is the pythonpath) and firebase_admin in another folder. And I add one path to firebase_admin cannot fix it. 
But the folder that has firebase_admin also has other subfolders like apache_beam, elasticsearch, etc. Why don't those libs also run into this issue? How do we install those other libs?
> But the folder that has firebase_admin also has other subfolders like apache_beam, elasticsearch, etc. Why don't those libs also run into this issue? How do we install those other libs?

![image](https://user-images.githubusercontent.com/85102180/222520790-1e3f73ba-71f2-449d-a5c8-59b1d1e1d878.png)
They are installed together but only firebase_admin couldn't import. It's so wired.
@462702985 I think your earlier comment (which you might have deleted) might be relevant here. You mentioned that core can import things but scripts cannot. This makes sense because the entry points are different. In core there is a bunch of setup that happens in install_third_party_libs.py beforehand and in scripts the scripts are just run directly. One way you could check this hypothesis is to e.g. put "import bleach" in the script and see whether that fails on first installation as well.

So I see two ways around this:

(a) Look at other deps that are directly invoked from scripts/ files and see how we install them. Can we install firebase_admin the same way? (Though will this cause confusion with the lib that is imported through the third-party installation process in start.py? It would be helpful to see if we have any such dependencies -- imported in both scripts and core -- and see if there's a pattern there we can follow.)

(b) The contributor_dashboard_debug script pattern is actually a bit unusual in our codebase, because normally when populating data we do it through the /admin page -- try starting the server, going to localhost:8181/admin, and looking at the Activities tab (which shows up on dev but not on prod). So the alternative would be to get rid of the script and put the functionality there instead just like we do for loading/populating other types of data. You might want to check with whoever wrote the script first before doing that (@qinghaoyang, I think, but perhaps loop in @sagangwee too).

I hope this helps give you some ways to move forward -- please let me know if it's useful and feel free to post follow-ups and further disoveries in this thread.
@seanlip  I found that in scripts folder, we can only import packages in pip list, however in core folder we can only import from third_party libs. I haven't find out why this happens. Using pip install firebase-admin is a fast solution, but we also need to change some packages version in requirements_dev.in. 
The stuff in scripts is run directly by the developer, whereas the core folder is run from within App Engine after it starts. I suspect App Engine has a different env to the developer, so this difference is expected.

Did you make any progress with suggestion (a) above?
> The stuff in scripts is run directly by the developer, whereas the core folder is run from within App Engine after it starts. I suspect App Engine has a different env to the developer, so this difference is expected.
> 
> Did you make any progress with suggestion (a) above?

I used pip install firebase admin and changed some related dependencies installation succeed. Do you think this method is correct
I think I'd need to know more about the results of your investigation. What other deps did you find that worked the same way? (The "why" behind the fix is always important to explain.)
![image](https://user-images.githubusercontent.com/85102180/222974942-2300e62a-5d38-4ea5-a0d8-a02e18e2273e.png)
see this pip list, there are some dependencies like webtest imported in scripts.
OK, I see, thanks. If that works then I guess you could use pip here as well, as long as it does not result in any confusion and actually fixes the issue.
When there are two google.cloud packages installed, import error occurs. How about we build third party lib before run scripts.start, so that scripts enviroment will be the same as core folder and we don't need to change code base.
![image](https://user-images.githubusercontent.com/85102180/223226986-9d57ec1d-5613-4c0a-923e-6086d069fce1.png)
@462702985 Don't we already do that at the top of scripts.start?
(Regardless, I think the main question is "does it work?", so once you've found a way that works, let us know. If it is conceptually understandable and won't confuse developers, it's probably OK.) 
I mean we type install_third_party in terminal before scripts.start, then we can find it in system path. It's better than type scripts.start twice.
It's a possible workaround, and we could update our installation instructions to do that (you may need to discuss with @U8NWXD). But how is this different from the call to install_third_party made at the top of the scripts.start file?
Hi @462702985, I dug into this a bit more and I have the following observations:

- The issue is arising due to contributor_dashboard_debug.py. This is a script that's used by Contributor Dashboard (CD) team members to generate data for the contributor dashboard. However, I was surprised to find that start.py imports this script, because (a) there is no reason why every developer (including non-CD team members) needs to have a dependency on it, and (b) we already have a standard process for generating data via the Admin > Activities tab, which this script violates.

- Given this, I think the correct thing to do is to at least remove the dependency of start.py on contributor_dashboard_debug.py, so that this doesn't affect the general Oppia developer community. This can be done in two ways: (a) keep the existing contributor_dashboard_debug.py script, but remove the start.py flag and have CD team members run the script separately in another terminal after starting the server, or (b) getting rid of the script entirely and using the established Admin > Activities workflow instead. Personally, I think that (b) is better since we'll have fewer design patterns to maintain, but I believe this is a discussion to be had between @qinghaoyang (who wrote the script) and @sagangwee @chris7716 (the CD team leads).

Based on the above, I would suggest that you pass this issue to @qinghaoyang for further action, and take something else up, since the fixes above are substantial and will require diving into the details of the Contributor Dashboard -- which is a fine thing to do in general, but in this case I think @qinghaoyang has more context. Do you think this sounds good? If so, please feel free to reassign this issue to @qinghaoyang. Thanks!
@seanlip It sounds good. I cannot assign people so can anyone do this?
Yup, updated. Thanks @462702985! 

By running,
`run scripts.start` again dint solve the problem for me.

Yet, it got fixed simply by installing the package using
`pip install firebase_admin`

and then running 

`python -m scripts.start
`
@qinghaoyang when do you aim to fix this?
> @qinghaoyang when do you aim to fix this?

@U8NWXD I plan to open a new PR by this weekend.
Interesting @Nik-09 how do you plan to proceed on this one ?

Hi, @legendarykamal I am yet to look at this issue completely.
I've run into this issue, as well. Should we be updating the onboarding documentation to include the suggested workaround from https://github.com/oppia/oppia/discussions/16855#discussioncomment-4633743?

/cc @seanlip since this relates to onboarding.
I ve run into this issue too. I think I downloaded firebase-admin using pip. It worked for me.
@462702985 will be working on this
I will check this issue this week.
I tried to modify system path and import directly from path but still not working. Any suggestion for what else I should try?
https://docs.google.com/document/d/1RydAE_CVcZdJ2JYpRsm_ltcg9IDOtrvjVfO_sRwVu3U/edit?usp=sharing

Hi @462702985 -- some questions:

(a) Are you able to reproduce the issue stated?
(b) If so, then when the installation fails, what is the PYTHONPATH, and does it include the path to firebase_admin?
(c) If other libs don't encounter this issue, then what is the difference between how those libs are installed and imported, vs firebase_admin? That's the main thing to investigate.

Basically, try to understand and construct a clear theory / mental model for _why_ the current issue is happening. Once you understand that fully, the fix should be easier to make.



> Hi @462702985 -- some questions:
> 
> (a) Are you able to reproduce the issue stated? (b) If so, then when the installation fails, what is the PYTHONPATH, and does it include the path to firebase_admin? (c) If other libs don't encounter this issue, then what is the difference between how those libs are installed and imported, vs firebase_admin? That's the main thing to investigate.
> 
> Basically, try to understand and construct a clear theory / mental model for _why_ the current issue is happening. Once you understand that fully, the fix should be easier to make.

I reproduced this issue and PYTHONPATH do not include path to firebase_admin. Other libs installed under pyenv folder(which is the pythonpath) and firebase_admin in another folder. And I add one path to firebase_admin cannot fix it. 
But the folder that has firebase_admin also has other subfolders like apache_beam, elasticsearch, etc. Why don't those libs also run into this issue? How do we install those other libs?
> But the folder that has firebase_admin also has other subfolders like apache_beam, elasticsearch, etc. Why don't those libs also run into this issue? How do we install those other libs?

![image](https://user-images.githubusercontent.com/85102180/222520790-1e3f73ba-71f2-449d-a5c8-59b1d1e1d878.png)
They are installed together but only firebase_admin couldn't import. It's so wired.
@462702985 I think your earlier comment (which you might have deleted) might be relevant here. You mentioned that core can import things but scripts cannot. This makes sense because the entry points are different. In core there is a bunch of setup that happens in install_third_party_libs.py beforehand and in scripts the scripts are just run directly. One way you could check this hypothesis is to e.g. put "import bleach" in the script and see whether that fails on first installation as well.

So I see two ways around this:

(a) Look at other deps that are directly invoked from scripts/ files and see how we install them. Can we install firebase_admin the same way? (Though will this cause confusion with the lib that is imported through the third-party installation process in start.py? It would be helpful to see if we have any such dependencies -- imported in both scripts and core -- and see if there's a pattern there we can follow.)

(b) The contributor_dashboard_debug script pattern is actually a bit unusual in our codebase, because normally when populating data we do it through the /admin page -- try starting the server, going to localhost:8181/admin, and looking at the Activities tab (which shows up on dev but not on prod). So the alternative would be to get rid of the script and put the functionality there instead just like we do for loading/populating other types of data. You might want to check with whoever wrote the script first before doing that (@qinghaoyang, I think, but perhaps loop in @sagangwee too).

I hope this helps give you some ways to move forward -- please let me know if it's useful and feel free to post follow-ups and further disoveries in this thread.
@seanlip  I found that in scripts folder, we can only import packages in pip list, however in core folder we can only import from third_party libs. I haven't find out why this happens. Using pip install firebase-admin is a fast solution, but we also need to change some packages version in requirements_dev.in. 
The stuff in scripts is run directly by the developer, whereas the core folder is run from within App Engine after it starts. I suspect App Engine has a different env to the developer, so this difference is expected.

Did you make any progress with suggestion (a) above?
> The stuff in scripts is run directly by the developer, whereas the core folder is run from within App Engine after it starts. I suspect App Engine has a different env to the developer, so this difference is expected.
> 
> Did you make any progress with suggestion (a) above?

I used pip install firebase admin and changed some related dependencies installation succeed. Do you think this method is correct
I think I'd need to know more about the results of your investigation. What other deps did you find that worked the same way? (The "why" behind the fix is always important to explain.)
![image](https://user-images.githubusercontent.com/85102180/222974942-2300e62a-5d38-4ea5-a0d8-a02e18e2273e.png)
see this pip list, there are some dependencies like webtest imported in scripts.
OK, I see, thanks. If that works then I guess you could use pip here as well, as long as it does not result in any confusion and actually fixes the issue.
When there are two google.cloud packages installed, import error occurs. How about we build third party lib before run scripts.start, so that scripts enviroment will be the same as core folder and we don't need to change code base.
![image](https://user-images.githubusercontent.com/85102180/223226986-9d57ec1d-5613-4c0a-923e-6086d069fce1.png)
@462702985 Don't we already do that at the top of scripts.start?
(Regardless, I think the main question is "does it work?", so once you've found a way that works, let us know. If it is conceptually understandable and won't confuse developers, it's probably OK.) 
I mean we type install_third_party in terminal before scripts.start, then we can find it in system path. It's better than type scripts.start twice.
It's a possible workaround, and we could update our installation instructions to do that (you may need to discuss with @U8NWXD). But how is this different from the call to install_third_party made at the top of the scripts.start file?
Hi @462702985, I dug into this a bit more and I have the following observations:

- The issue is arising due to contributor_dashboard_debug.py. This is a script that's used by Contributor Dashboard (CD) team members to generate data for the contributor dashboard. However, I was surprised to find that start.py imports this script, because (a) there is no reason why every developer (including non-CD team members) needs to have a dependency on it, and (b) we already have a standard process for generating data via the Admin > Activities tab, which this script violates.

- Given this, I think the correct thing to do is to at least remove the dependency of start.py on contributor_dashboard_debug.py, so that this doesn't affect the general Oppia developer community. This can be done in two ways: (a) keep the existing contributor_dashboard_debug.py script, but remove the start.py flag and have CD team members run the script separately in another terminal after starting the server, or (b) getting rid of the script entirely and using the established Admin > Activities workflow instead. Personally, I think that (b) is better since we'll have fewer design patterns to maintain, but I believe this is a discussion to be had between @qinghaoyang (who wrote the script) and @sagangwee @chris7716 (the CD team leads).

Based on the above, I would suggest that you pass this issue to @qinghaoyang for further action, and take something else up, since the fixes above are substantial and will require diving into the details of the Contributor Dashboard -- which is a fine thing to do in general, but in this case I think @qinghaoyang has more context. Do you think this sounds good? If so, please feel free to reassign this issue to @qinghaoyang. Thanks!
@seanlip It sounds good. I cannot assign people so can anyone do this?
Yup, updated. Thanks @462702985! 

By running,
`run scripts.start` again dint solve the problem for me.

Yet, it got fixed simply by installing the package using
`pip install firebase_admin`

and then running 

`python -m scripts.start
`
@qinghaoyang when do you aim to fix this?
> @qinghaoyang when do you aim to fix this?

@U8NWXD I plan to open a new PR by this weekend.

Resolving patch:
diff --git a/scripts/generate_sample_data.py b/scripts/populate_sample_contributor_data.py
similarity index 78%
rename from scripts/generate_sample_data.py
rename to scripts/populate_sample_contributor_data.py
--- a/scripts/generate_sample_data.py
+++ b/scripts/populate_sample_contributor_data.py
@@ -14,10 +14,8 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
-"""This script assists development of the contributor dashboard and testing of
-Android build process. When the "--generate_sample_data" flag is enabled,
-the start script will call the "generate_sample_data" function, which does the
-following:
+"""This script populates sample data for assisting development. After starting
+up a development server running Oppia, running this script does the following:
     1. Create an admin user with the username "a" and assign curriculum,
     translation, and question admin rights to "a".
     2. Create a non-admin user with the username "b" and give the user "submit
@@ -27,28 +25,35 @@
     4. Add the topics linked to the lessons above to a classroom. This is needed
     for the topics, if published, to show up in the topic selectors and for the
     linked opportunities to show up in the "Submit Question" tab.
+Note: The standard process for generating data is via the Admin > Activities
+tab. Here is why we use a script but not the standard process to execute the
+above workflow. In the above workflow, creating new users requires beginning
+and ending sessions several times, but ending sessions on the Admin page causes
+a "Registration session expired" error. This issue should be resolved in future
+if we want to execute the above workflow on the Admin page.
 """
 
 from __future__ import annotations
 
-import contextlib
 import hashlib
 import json
-import os
 
 from core import feconf
 from core.constants import constants
 
-import firebase_admin
-from firebase_admin import auth as firebase_auth
 import requests
-from typing import Dict, Final, Iterator, List
+from typing import Dict, Final, List
+
+PORT_NUMBER_FOR_GAE_SERVER: Final = 8181
 
 FIREBASE_AUTH_EMULATOR_HOST: Final = (
     'localhost:%s' % feconf.FIREBASE_EMULATOR_PORT)
 FIREBASE_SIGN_IN_URL: Final = (
     'http://%s/identitytoolkit.googleapis.com/v1/accounts:signInWithPassword'
     % FIREBASE_AUTH_EMULATOR_HOST)
+FIREBASE_SIGN_UP_URL: Final = (
+    'http://%s/identitytoolkit.googleapis.com/v1/accounts:signUp'
+    % FIREBASE_AUTH_EMULATOR_HOST)
 
 SUPER_ADMIN_EMAIL: Final = feconf.ADMIN_EMAIL_ADDRESS
 SUPER_ADMIN_USERNAME: Final = 'a'
@@ -63,7 +68,7 @@
 CLASSROOM_URL_FRAGMENT: Final = 'math'
 
 
-class GenerateSampleData:
+class SampleDataInitializer:
     """Contains functions that populate sample data by sending requests to the
     development server.
 
@@ -79,11 +84,10 @@ def __init__(self, base_url: str) -> None:
         self.base_url = base_url
         self.csrf_token = ''
 
-    def generate_sample_data(self) -> None:
-        """Generate sample data."""
-        firebase_admin.initialize_app(
-            options={'projectId': feconf.OPPIA_PROJECT_ID})
-
+    def populate_data(self) -> None:
+        """Populate sample data to help develop for the contributor
+        dashboard.
+        """
         self._sign_up_new_user(SUPER_ADMIN_EMAIL, SUPER_ADMIN_USERNAME)
         self._sign_up_new_user(CONTRIBUTOR_EMAIL, CONTRIBUTOR_USERNAME)
 
@@ -108,17 +112,9 @@ def _sign_up_new_user(self, email: str, username: str) -> None:
         # this script, clicking the "Sign In" button will generate a valid
         # password which is the same as that in the emulator.
         password = hashlib.md5(email.encode('utf-8')).hexdigest()
+        token_id = self._sign_up_on_firebase(email, password)
+        self._begin_session(token_id)
 
-        # The FIREBASE_AUTH_EMULATOR_HOST environment variable is set to connect
-        # with the Firebase Authentication emulator.
-        with self._set_environ(
-            'FIREBASE_AUTH_EMULATOR_HOST', FIREBASE_AUTH_EMULATOR_HOST
-        ):
-            # Create a new user in Firebase.
-            firebase_auth.create_user(email=email, password=password)
-
-        # Sign up the new user in Oppia and set its username.
-        self._sign_in(email)
         self._make_request('GET', '/signup?return_url=/')
         self.csrf_token = self._get_csrf_token()
 
@@ -140,34 +136,31 @@ def _sign_up_new_user(self, email: str, username: str) -> None:
         self._make_request('GET', '/session_end')
         self.csrf_token = ''
 
-    @contextlib.contextmanager
-    def _set_environ(self, env_name: str, value: str) -> Iterator[None]:
-        """Temporarily set the environment variable 'env_name' to 'value' while
-        inside the ``with`` block.
-        """
-        old_value = os.environ.get(env_name)
-        os.environ[env_name] = value
-        try:
-            yield
-        finally:
-            if old_value is None:
-                del os.environ[env_name]
-            else:
-                os.environ[env_name] = old_value
+    def _sign_up_on_firebase(
+        self, email: str, password: str
+    ) -> str:
+        """Signs up on Firebase, and returns the token id."""
+        token_id = requests.post(
+            FIREBASE_SIGN_UP_URL,
+            params={'key': 'fake-api-key'},
+            json={
+                'email': email,
+                'password': password
+            }
+        ).json()['idToken']
+
+        return str(token_id)
 
     def _sign_in(self, email: str) -> None:
         """Begins a session with the given email, i.e. log in with the email."""
         password = hashlib.md5(email.encode('utf-8')).hexdigest()
+        token_id = self._sign_in_on_firebase(email, password)
+        self._begin_session(token_id)
 
-        token_id = self._sign_in_with_email_and_password(email, password)
-        headers = {'Authorization': 'Bearer %s' % token_id}
-
-        self._make_request('GET', '/session_begin', headers=headers)
-
-    def _sign_in_with_email_and_password(
+    def _sign_in_on_firebase(
         self, email: str, password: str
-    ) -> str | None:
-        """Signs in with email and password, and returns the token id."""
+    ) -> str:
+        """Signs in on Firebase, and returns the token id."""
         token_id = requests.post(
             FIREBASE_SIGN_IN_URL,
             params={'key': 'fake-api-key'},
@@ -177,7 +170,12 @@ def _sign_in_with_email_and_password(
             }
         ).json()['idToken']
 
-        return str(token_id) if token_id else None
+        return str(token_id)
+
+    def _begin_session(self, token_id: str) -> None:
+        """Begins a session with the given token id."""
+        headers = {'Authorization': 'Bearer %s' % token_id}
+        self._make_request('GET', '/session_begin', headers=headers)
 
     def _get_csrf_token(self) -> str:
         """Gets the CSRF token."""
@@ -267,3 +265,20 @@ def _make_request(
             method, self.base_url + url, headers=headers, params=params)
 
         return response
+
+
+def main() -> None:
+    """Populates data for contributor dashboard debugging."""
+    initializer = (
+        SampleDataInitializer(
+            base_url='http://localhost:%s' % PORT_NUMBER_FOR_GAE_SERVER
+        )
+    )
+    initializer.populate_data()
+
+
+# The 'no coverage' pragma is used as this line is un-testable. This is because
+# it will only be called when populate_sample_contributor_data.py used as a
+# script.
+if __name__ == '__main__': # pragma: no cover
+    main()
diff --git a/scripts/start.py b/scripts/start.py
--- a/scripts/start.py
+++ b/scripts/start.py
@@ -37,7 +37,6 @@
 from . import servers # isort:skip  pylint: disable=wrong-import-position, wrong-import-order
 
 from core.constants import constants # isort:skip  pylint: disable=wrong-import-position, wrong-import-order
-from scripts import generate_sample_data # isort:skip  pylint: disable=wrong-import-position, wrong-import-order
 
 _PARSER = argparse.ArgumentParser(
     description="""
@@ -78,15 +77,6 @@
     '--source_maps',
     help='optional; if specified, build webpack with source maps.',
     action='store_true')
-_PARSER.add_argument(
-    '--contributor_dashboard_debug',
-    help='DEPRECATED: use \'--generate_sample_data\' instead',
-    action='store_true')
-_PARSER.add_argument(
-    '--generate_sample_data',
-    help='optional; if specified, populate sample data that can be used to help'
-         'develop for the contributor dashboard.',
-    action='store_true')
 
 PORT_NUMBER_FOR_GAE_SERVER = 8181
 
@@ -140,12 +130,6 @@ def main(args: Optional[Sequence[str]] = None) -> None:
     """Starts up a development server running Oppia."""
     parsed_args = _PARSER.parse_args(args=args)
 
-    if parsed_args.contributor_dashboard_debug:
-        raise Exception(
-            'The \'--contributor_dashboard_debug\' flag is deprecated'
-            'use \'--generate_sample_data\' instead.'
-        )
-
     if common.is_port_in_use(PORT_NUMBER_FOR_GAE_SERVER):
         common.print_each_string_after_two_new_lines([
             'WARNING',
@@ -198,13 +182,6 @@ def main(args: Optional[Sequence[str]] = None) -> None:
             skip_sdk_update_check=True,
             port=PORT_NUMBER_FOR_GAE_SERVER))
 
-        if parsed_args.generate_sample_data:
-            initializer = (
-                generate_sample_data.GenerateSampleData(
-                    base_url='http://localhost:%s' % PORT_NUMBER_FOR_GAE_SERVER)
-            )
-            initializer.generate_sample_data()
-
         if parsed_args.no_browser:
             common.print_each_string_after_two_new_lines([
                 'INFORMATION',


Test patch:
diff --git a/scripts/backend_test_shards.json b/scripts/backend_test_shards.json
--- a/scripts/backend_test_shards.json
+++ b/scripts/backend_test_shards.json
@@ -315,7 +315,7 @@
         "core.jobs.batch_jobs.rejecting_suggestion_for_invalid_content_ids_jobs_test",
         "core.jobs.batch_jobs.user_settings_profile_picture_jobs_test",
         "core.jobs.io.cache_io_test",
-        "scripts.generate_sample_data_test",
+        "scripts.populate_sample_contributor_data_test",
         "core.platform.secrets.cloud_secrets_services_test",
         "extensions.interactions.ItemSelectionInput.ItemSelectionInput_test",
         "extensions.interactions.SetInput.SetInput_test",
diff --git a/scripts/generate_sample_data_test.py b/scripts/populate_sample_contributor_data_test.py
similarity index 59%
rename from scripts/generate_sample_data_test.py
rename to scripts/populate_sample_contributor_data_test.py
--- a/scripts/generate_sample_data_test.py
+++ b/scripts/populate_sample_contributor_data_test.py
@@ -14,13 +14,10 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
-"""Unit tests for scripts/generate_sample_data.py."""
+"""Unit tests for scripts/populate_sample_data.py."""
 
 from __future__ import annotations
 
-import hashlib
-import os
-
 from core import feconf
 from core.controllers import base
 from core.domain import opportunity_services
@@ -33,10 +30,8 @@
 from core.platform.auth import firebase_auth_services_test
 from core.tests import test_utils
 
-from scripts import generate_sample_data
+from scripts import populate_sample_contributor_data
 
-import firebase_admin
-from firebase_admin import auth as firebase_auth
 import requests
 from typing import Dict, List, Optional
 import webtest
@@ -67,47 +62,37 @@ def json(self) -> Dict[str, str]:
         return self.json_dict
 
 
-class GenerateSampleDataTests(test_utils.GenericTestBase):
+class SampleDataInitializerTests(test_utils.GenericTestBase):
 
     def setUp(self) -> None:
+        self.AUTO_CREATE_DEFAULT_SUPERADMIN_USER = False
         super().setUp()
         self.firebase_sdk_stub = (
             firebase_auth_services_test.FirebaseAdminSdkStub())
         self.firebase_sdk_stub.install(self)
         self.token_by_email: Dict[str, str] = {}
         self.token_of_current_user: Optional[str] = None
-        self.initializer = generate_sample_data.GenerateSampleData(base_url='')
+        self.initializer = (
+            populate_sample_contributor_data.SampleDataInitializer(
+                base_url=''))
 
         self.initializer.csrf_token = self.get_new_csrf_token()
 
         self.request_swap = self.swap(
-            self.initializer.session, 'request', self._mock_request)
-
-        self.init_app_swap = self.swap_with_call_counter(
-            firebase_admin, 'initialize_app')
-
-        admin_password = hashlib.md5(
-            generate_sample_data.SUPER_ADMIN_EMAIL.encode('utf-8')).hexdigest()
-        contributor_password = hashlib.md5(
-            generate_sample_data.CONTRIBUTOR_EMAIL.encode('utf-8')).hexdigest()
-        self.create_user_swap = self.swap_with_checks(
-            firebase_auth, 'create_user', self._mock_firebase_auth_create_user,
-            expected_kwargs=[
-                {
-                    'email': generate_sample_data.SUPER_ADMIN_EMAIL,
-                    'password': admin_password
-                },
-                {
-                    'email': generate_sample_data.CONTRIBUTOR_EMAIL,
-                    'password': contributor_password
-                }
+            self.initializer.session,
+            'request',
+            self._mock_request)
+
+        self.post_to_firebase_swap = self.swap_with_checks(
+            requests, 'post',
+            self._mock_post_to_firebase,
+            expected_args=[
+                (populate_sample_contributor_data.FIREBASE_SIGN_UP_URL,),
+                (populate_sample_contributor_data.FIREBASE_SIGN_UP_URL,),
+                (populate_sample_contributor_data.FIREBASE_SIGN_IN_URL,)
             ]
         )
 
-        self.begin_session_swap = self.swap(
-            self.initializer, '_sign_in',
-            self._mock_login_as_admin)
-
     def tearDown(self) -> None:
         self.firebase_sdk_stub.uninstall()
         self.logout()
@@ -138,37 +123,55 @@ def _mock_request(
         if method == 'PUT':
             return self.testapp.put(url, params=params, headers=headers)
 
-    def _mock_firebase_auth_create_user(self, **kwargs: str) -> None:
-        """Mock for firebase_auth.create_user()."""
-        email = kwargs['email']
-        auth_id = self.get_auth_id_from_email(email)
-        self.token_by_email[email] = (
-            self.firebase_sdk_stub.create_user(auth_id, email))
-        print('token_by_email: %s' % self.token_by_email)
+    def _mock_post_to_firebase(
+        self,
+        url: str,
+        **kwargs: Dict[str, str]
+    ) -> MockResponse | None:
+        """Mock for the post request to Firebase. The request is for signing
+        up or signing in a user on Firebase.
+        """
+        email = kwargs['json']['email']
+
+        if url == populate_sample_contributor_data.FIREBASE_SIGN_UP_URL:
+            auth_id = self.get_auth_id_from_email(email)
+            self.token_by_email[email] = (
+                self.firebase_sdk_stub.create_user(auth_id, email))
+
+        self.token_of_current_user = self.token_by_email[email]
+        return MockResponse(json={'idToken': self.token_of_current_user})
 
     def _mock_login_as_admin(self, email: str) -> None:
         """Sets the environment variables to simulate a login of admin."""
         self.login(email, is_super_admin=True)
 
-    def test_generate_sample_data(self) -> None:
-        with self.request_swap, self.create_user_swap, self.begin_session_swap:
-            with self.init_app_swap as init_app_counter:
-                self.initializer.generate_sample_data()
+    def test_populate_data_is_called(self) -> None:
+        populate_data_swap = self.swap_with_call_counter(
+            populate_sample_contributor_data.SampleDataInitializer,
+            'populate_data')
+        with populate_data_swap as call_counter:
+            populate_sample_contributor_data.main()
 
-        self.assertEqual(init_app_counter.times_called, 1)
-        # Asserts that the function _mock_login_as_admin() is called.
-        self.assertEqual(
-            os.environ['USER_EMAIL'], generate_sample_data.SUPER_ADMIN_EMAIL)
-        self.assertEqual(os.environ['USER_IS_ADMIN'], '1')
+        self.assertEqual(call_counter.times_called, 1)
 
+    def test_populate_data(self) -> None:
+        with self.request_swap, self.post_to_firebase_swap:
+            self.initializer.populate_data()
+
+        self._assert_sign_up_new_user(
+            populate_sample_contributor_data.SUPER_ADMIN_EMAIL,
+            populate_sample_contributor_data.SUPER_ADMIN_USERNAME)
+        self._assert_sign_up_new_user(
+            populate_sample_contributor_data.CONTRIBUTOR_EMAIL,
+            populate_sample_contributor_data.CONTRIBUTOR_USERNAME)
         self._assert_user_roles(
-            generate_sample_data.SUPER_ADMIN_USERNAME,
-            generate_sample_data.SUPER_ADMIN_ROLES)
+            populate_sample_contributor_data.SUPER_ADMIN_USERNAME,
+            populate_sample_contributor_data.SUPER_ADMIN_ROLES)
         self._assert_can_submit_question_suggestions(
-            generate_sample_data.CONTRIBUTOR_USERNAME)
+            populate_sample_contributor_data.CONTRIBUTOR_USERNAME)
         self._assert_generate_sample_new_structures_data()
         self._assert_topics_in_classroom(
-            generate_sample_data.CLASSROOM_NAME)
+            populate_sample_contributor_data.CLASSROOM_NAME)
 
     def _assert_user_roles(self, username: str, roles: List[str]) -> None:
         """Asserts that the user has the given roles."""
@@ -224,31 +227,6 @@ def _assert_topics_in_classroom(self, classroom_name: str) -> None:
             self.assertIn(
                 topic_summary_dict, classroom_dict['topic_summary_dicts'])
 
-    def test_sign_up_new_user(self) -> None:
-        auth_host = os.environ.get('FIREBASE_AUTH_EMULATOR_HOST')
-        if auth_host is None:
-            os.environ['FIREBASE_AUTH_EMULATOR_HOST'] = '0000'
-
-        with self.request_swap, self.create_user_swap, self.begin_session_swap:
-            with self.init_app_swap as init_app_counter:
-                self.initializer.generate_sample_data()
-
-        self.assertEqual(init_app_counter.times_called, 1)
-        # Asserts that the environment variable 'FIREBASE_AUTH_EMULATOR_HOST' is
-        # not changed after calling the function populate_debug_data().
-        if auth_host is None:
-            self.assertEqual(os.environ['FIREBASE_AUTH_EMULATOR_HOST'], '0000')
-            del os.environ['FIREBASE_AUTH_EMULATOR_HOST']
-        else:
-            self.assertNotIn('FIREBASE_AUTH_EMULATOR_HOST', auth_host)
-
-        self._assert_sign_up_new_user(
-            generate_sample_data.SUPER_ADMIN_EMAIL,
-            generate_sample_data.SUPER_ADMIN_USERNAME)
-        self._assert_sign_up_new_user(
-            generate_sample_data.CONTRIBUTOR_EMAIL,
-            generate_sample_data.CONTRIBUTOR_USERNAME)
-
     def _assert_sign_up_new_user(self, email: str, username: str) -> None:
         """Asserts that the function _mock_firebase_auth_create_user() is called
         and a user with the given email and username is created.
@@ -257,37 +235,3 @@ def _assert_sign_up_new_user(self, email: str, username: str) -> None:
         user_settings = user_services.get_user_settings_from_email(email)
         assert user_settings is not None
         self.assertEqual(user_settings.username, username)
-
-    def test_sign_in(self) -> None:
-        sign_in_swap = self.swap_with_checks(
-            requests, 'post', self._mock_firebase_auth_sign_in,
-            expected_args=[
-                (generate_sample_data.FIREBASE_SIGN_IN_URL,),
-                (generate_sample_data.FIREBASE_SIGN_IN_URL,),
-                (generate_sample_data.FIREBASE_SIGN_IN_URL,)
-            ]
-        )
-
-        with self.request_swap, self.create_user_swap:
-            with sign_in_swap, self.init_app_swap as init_app_counter:
-                self.initializer.generate_sample_data()
-
-        self.assertEqual(init_app_counter.times_called, 1)
-        # Asserts that the function _mock_firebase_auth_sign_in() is called.
-        self.assertEqual(
-            self.token_of_current_user,
-            self.token_by_email[generate_sample_data.SUPER_ADMIN_EMAIL])
-        # Asserts that the function mock_establish_auth_session() is called.
-        self.assertEqual(
-            os.environ['USER_EMAIL'], generate_sample_data.SUPER_ADMIN_EMAIL)
-        self.assertEqual(os.environ['USER_IS_ADMIN'], '1')
-
-    def _mock_firebase_auth_sign_in(
-        self, _: str, **kwargs: Dict[str, str]
-    ) -> MockResponse:
-        """Mock for the post request to FIREBASE_SIGN_IN_URL, where the response
-        including the token information.
-        """
-        email = kwargs['json']['email']
-        self.token_of_current_user = self.token_by_email[email]
-        return MockResponse(json={'idToken': self.token_of_current_user})
diff --git a/scripts/start_test.py b/scripts/start_test.py
--- a/scripts/start_test.py
+++ b/scripts/start_test.py
@@ -21,7 +21,6 @@
 from scripts import build
 from scripts import common
 from scripts import extend_index_yaml
-from scripts import generate_sample_data
 from scripts import install_third_party_libs
 from scripts import servers
 
@@ -99,7 +98,7 @@ def mock_context_manager() -> MockCompilerContextManager:
                 'enable_host_checking': True,
                 'automatic_restart': True,
                 'skip_sdk_update_check': True,
-                'port': PORT_NUMBER_FOR_GAE_SERVER,
+                'port': PORT_NUMBER_FOR_GAE_SERVER
             }])
         self.swap_create_server = self.swap_with_checks(
             servers, 'create_managed_web_browser',
@@ -253,49 +252,6 @@ def test_source_maps_are_compiled_by_webpack(self) -> None:
             ],
             self.print_arr)
 
-    def test_start_servers_with_contributor_dashboard_debug_flag_fails(
-        self
-    ) -> None:
-        with self.swap_install_third_party_libs:
-            from scripts import start
-        with self.assertRaisesRegex(
-            Exception,
-            'The \'--contributor_dashboard_debug\' flag is deprecated'
-            'use \'--generate_sample_data\' instead.'
-        ):
-            start.main(args=['--contributor_dashboard_debug'])
-
-    def test_start_servers_successfully_with_generate_sample_data_flag(
-        self
-    ) -> None:
-        with self.swap_install_third_party_libs:
-            from scripts import start
-        swap_build = self.swap_with_checks(
-            build, 'main', lambda **unused_kwargs: None,
-            expected_kwargs=[{'args': []}])
-        populate_data_swap = self.swap_with_call_counter(
-            generate_sample_data.GenerateSampleData, 'generate_sample_data'
-        )
-        with self.swap_cloud_datastore_emulator, self.swap_ng_build, swap_build:
-            with self.swap_elasticsearch_dev_server, self.swap_redis_server:
-                with self.swap_create_server, self.swap_webpack_compiler:
-                    with self.swap_extend_index_yaml, self.swap_dev_appserver:
-                        with self.swap_firebase_auth_emulator, self.swap_print:
-                            with populate_data_swap as populate_data_counter:
-                                start.main(args=['--generate_sample_data'])
-
-        self.assertEqual(populate_data_counter.times_called, 1)
-        self.assertIn(
-            [
-                'INFORMATION',
-                (
-                    'Local development server is ready! Opening a default web '
-                    'browser window pointing to it: '
-                    'http://localhost:%s/' % PORT_NUMBER_FOR_GAE_SERVER
-                )
-            ],
-            self.print_arr)
-
     def test_could_not_auto_launch_web_browser(self) -> None:
         with self.swap_install_third_party_libs:
             from scripts import start