Repository: oppia/oppia

Issue:
Make instruction to run scripts.start again clearer
Currently, when developers have out-of-date dependencies and need to run `python -m scripts.start` again, they get this confusing error message:

![](https://files.gitter.im/561f4f4416b6c7089cb70f23/yT31/Capture.PNG)

We should eliminate the traceback and give clear instructions
Make instruction to run scripts.start again clearer
Currently, when developers have out-of-date dependencies and need to run `python -m scripts.start` again, they get this confusing error message:

![](https://files.gitter.im/561f4f4416b6c7089cb70f23/yT31/Capture.PNG)

We should eliminate the traceback and give clear instructions


Discussion:
When I tested this locally by changing the version in `requirements.txt`, the output was correct:

```
Your currently installed python libraries do not match the
libraries listed in your "requirements.txt" file. Here is a
full list of library/version discrepancies:

Library                             |Requirements Version     |Currently Installed Version
attrs                               |20.3.1                   |20.3.0                   


Please fix these discrepancies by editing the `requirements.in`
file or running `scripts.install_third_party` to regenerate
the `third_party/python_libs` directory.
```

However, I was able to reproduce the error if a library was missing from `third_party/python_libs`:

```
Your currently installed python libraries do not match the
libraries listed in your "requirements.txt" file. Here is a
full list of library/version discrepancies:

Library                             |Requirements Version     |Currently Installed Version
Traceback (most recent call last):
  File ".git/hooks/pre-push", line 573, in <module>
    main()
  File ".git/hooks/pre-push", line 512, in main
    check_for_backend_python_library_inconsistencies()
  File ".git/hooks/pre-push", line 474, in check_for_backend_python_library_inconsistencies
    library_name, version_strings[0], version_strings[1]))
TypeError: unsupported format string passed to NoneType.__format__
```

The problem appears to be that our format string doesn't work for objects of type `None`:

```python
            python_utils.PRINT('{:<35} |{:<25}|{:<25}'.format(
```

The solution is to force the objects being inserted into the format string to be strings first using `!s`. See https://docs.python.org/3/library/string.html#format-string-syntax for details
When I tested this locally by changing the version in `requirements.txt`, the output was correct:

```
Your currently installed python libraries do not match the
libraries listed in your "requirements.txt" file. Here is a
full list of library/version discrepancies:

Library                             |Requirements Version     |Currently Installed Version
attrs                               |20.3.1                   |20.3.0                   


Please fix these discrepancies by editing the `requirements.in`
file or running `scripts.install_third_party` to regenerate
the `third_party/python_libs` directory.
```

However, I was able to reproduce the error if a library was missing from `third_party/python_libs`:

```
Your currently installed python libraries do not match the
libraries listed in your "requirements.txt" file. Here is a
full list of library/version discrepancies:

Library                             |Requirements Version     |Currently Installed Version
Traceback (most recent call last):
  File ".git/hooks/pre-push", line 573, in <module>
    main()
  File ".git/hooks/pre-push", line 512, in main
    check_for_backend_python_library_inconsistencies()
  File ".git/hooks/pre-push", line 474, in check_for_backend_python_library_inconsistencies
    library_name, version_strings[0], version_strings[1]))
TypeError: unsupported format string passed to NoneType.__format__
```

The problem appears to be that our format string doesn't work for objects of type `None`:

```python
            python_utils.PRINT('{:<35} |{:<25}|{:<25}'.format(
```

The solution is to force the objects being inserted into the format string to be strings first using `!s`. See https://docs.python.org/3/library/string.html#format-string-syntax for details

Resolving patch:
diff --git a/scripts/pre_push_hook.py b/scripts/pre_push_hook.py
--- a/scripts/pre_push_hook.py
+++ b/scripts/pre_push_hook.py
@@ -470,7 +470,7 @@ def check_for_backend_python_library_inconsistencies():
                 'Library', 'Requirements Version',
                 'Currently Installed Version'))
         for library_name, version_strings in mismatches.items():
-            python_utils.PRINT('{:<35} |{:<25}|{:<25}'.format(
+            python_utils.PRINT('{!s:<35} |{!s:<25}|{!s:<25}'.format(
                 library_name, version_strings[0], version_strings[1]))
         python_utils.PRINT('\n')
         common.print_each_string_after_two_new_lines([


Test patch:
diff --git a/scripts/pre_push_hook_test.py b/scripts/pre_push_hook_test.py
--- a/scripts/pre_push_hook_test.py
+++ b/scripts/pre_push_hook_test.py
@@ -793,6 +793,43 @@ def mock_exit_error(error_code):
                 '`third_party/python_libs` directory.\n\n'
             ])
 
+    def test_main_exits_when_missing_backend_python_lib(self):
+        """Test that main exits with correct error message when a python
+        library required in `requirements.txt` is missing in
+        `third_party/python_libs`.
+        """
+        def mock_get_mismatches():
+            return {
+                'library': ('version', None)
+            }
+
+        def mock_exit_error(error_code):
+            self.assertEqual(error_code, 1)
+
+        swap_get_mismatches = self.swap(
+            install_backend_python_libs, 'get_mismatches',
+            mock_get_mismatches)
+        swap_sys_exit = self.swap(sys, 'exit', mock_exit_error)
+        with self.print_swap, swap_sys_exit, swap_get_mismatches:
+            pre_push_hook.check_for_backend_python_library_inconsistencies()
+
+        self.assertEqual(
+            self.print_arr,
+            [
+                'Your currently installed python libraries do not match the\n'
+                'libraries listed in your "requirements.txt" file. Here is a\n'
+                'full list of library/version discrepancies:\n',
+                'Library                             |Requirements Version     '
+                '|Currently Installed Version',
+                'library                             |version                  '
+                '|None                     ',
+                '\n',
+                'Please fix these discrepancies by editing the '
+                '`requirements.in`\nfile or running '
+                '`scripts.install_third_party` to regenerate\nthe '
+                '`third_party/python_libs` directory.\n\n'
+            ])
+
     def test_main_with_no_inconsistencies_in_backend_python_libs(self):
         def mock_get_mismatches():
             return {}