Repository: oppia/oppia

Issue:
Use pip_install for pyyaml and future.
<!--
  - Thanks for taking the time to report a bug in the Oppia project.
  - Before filing a new issue, please do a quick search to check that it hasn't
  - already been filed on the [issue tracker](https://github.com/oppia/oppia/issues)._
  -->

**Describe the bug**
Installation errors described in [8123](https://github.com/oppia/oppia/issues/8123 )  still occur when running python -m scripts.install_third_party_libs. 



**Desktop (please complete the following information; delete this section if the issue does not arise on desktop):**
 - OS: Ubuntu 18.04
 - pip: 9.0.1

**Additional context**
use pip_install to install the above packages



Discussion:
@vojtechjelinek @kevinlee12 Please take a look
@amey-kudari can you provide an example of that this would look like?
Hi @kevinlee12 ! 
Yeah sure! This is what I wanted to do.
Change this
```
if not os.path.exists(os.path.join('third_party', 'future-0.17.1')):    
    subprocess.check_call([    
        sys.executable, '-m', 'pip', 'install', 'future==0.17.1', '--target',    
        os.path.join('third_party', 'future-0.17.1')]) 
```
To this
```
if not os.path.exists(os.path.join('third_party', 'future-0.17.1')):
  pip_install('future','0.17.1',os.path.join('third_party', 'future-0.17.1'))
```
and do it after defining pip_install.
That way exceptions get handled by pip_install.
sure, as long as it also works on Windows, go for it!

On Sun, Feb 9, 2020 at 1:44 PM XxFALCONxX <notifications@github.com> wrote:

> Hi @kevinlee12 <https://github.com/kevinlee12> !
> Yeah sure! This is what I wanted to do.
> Change this
>
> if not os.path.exists(os.path.join('third_party', 'future-0.17.1')):
>     subprocess.check_call([
>         sys.executable, '-m', 'pip', 'install', 'future==0.17.1', '--target',
>         os.path.join('third_party', 'future-0.17.1')])
>
> To this
>
> if not os.path.exists(os.path.join('third_party', 'future-0.17.1')):
>   pip_install('future','0.17.1',os.path.join('third_party', 'future-0.17.1'))
>
> and do it after defining pip_install.
> That way exceptions get handled by pip_install.
>
> —
> You are receiving this because you were mentioned.
> Reply to this email directly, view it on GitHub
> <https://github.com/oppia/oppia/issues/8586?email_source=notifications&email_token=ABL52MB6ZQ57OA4LTQCIT73RCB2LLA5CNFSM4KSAGW6KYY3PNVWWK3TUL52HS4DFVREXG43VMVBW63LNMVXHJKTDN5WW2ZLOORPWSZGOELGZBTA#issuecomment-583897292>,
> or unsubscribe
> <https://github.com/notifications/unsubscribe-auth/ABL52MCXSFUDVOGSNFWNCYTRCB2LLANCNFSM4KSAGW6A>
> .
>


Resolving patch:
diff --git a/scripts/install_third_party_libs.py b/scripts/install_third_party_libs.py
--- a/scripts/install_third_party_libs.py
+++ b/scripts/install_third_party_libs.py
@@ -22,19 +22,28 @@
 import subprocess
 import sys
 
-# These libraries need to be installed before running or importing any script.
+
 TOOLS_DIR = os.path.join(os.pardir, 'oppia_tools')
-# Download and install pyyaml.
-if not os.path.exists(os.path.join(TOOLS_DIR, 'pyyaml-5.1.2')):
-    subprocess.check_call([
-        sys.executable, '-m', 'pip', 'install', 'pyyaml==5.1.2', '--target',
-        os.path.join(TOOLS_DIR, 'pyyaml-5.1.2')])
-
-# Download and install future.
-if not os.path.exists(os.path.join('third_party', 'future-0.17.1')):
-    subprocess.check_call([
-        sys.executable, '-m', 'pip', 'install', 'future==0.17.1', '--target',
-        os.path.join('third_party', 'future-0.17.1')])
+
+# These libraries need to be installed before running or importing any script.
+
+PREREQUISITES = [
+    ('pyyaml', '5.1.2', os.path.join(TOOLS_DIR, 'pyyaml-5.1.2')),
+    ('future', '0.17.1', os.path.join('third_party', 'future-0.17.1')),
+]
+
+for package_name, version_number, target_path in PREREQUISITES:
+    if not os.path.exists(target_path):
+        command_text = [
+            sys.executable, '-m', 'pip', 'install', '%s==%s'
+            % (package_name, version_number), '--target', target_path]
+        uextention_text = ['--user', '--prefix=', '--system']
+        current_process = subprocess.Popen(
+            command_text, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
+        output_stderr = current_process.communicate()[1]
+        if 'can\'t combine user with prefix' in output_stderr:
+            subprocess.check_call(command_text + uextention_text)
+
 
 # pylint: disable=wrong-import-position
 # pylint: disable=wrong-import-order


Test patch:
diff --git a/scripts/script_import_test.py b/scripts/script_import_test.py
--- a/scripts/script_import_test.py
+++ b/scripts/script_import_test.py
@@ -52,20 +52,37 @@ class InstallThirdPartyLibsImportTests(test_utils.GenericTestBase):
     def setUp(self):
         super(InstallThirdPartyLibsImportTests, self).setUp()
         self.commands = []
+        # pylint: disable=unused-argument
+        # pylint: disable=super-init-not-called
+        def mock_popen_error_call(unused_cmd_tokens, *args, **kwargs):
+            class Ret(test_utils.GenericTestBase):
+                """Return object that gives user-prefix error."""
+                def __init__(self):
+                    self.returncode = 1
+                def communicate(self):
+                    """Return user-prefix error as stderr."""
+                    return '', 'can\'t combine user with prefix'
+            return Ret()
         def mock_check_call(cmd_tokens):
             self.commands.extend(cmd_tokens)
+        self.Popen_swap = self.swap(
+            subprocess, 'Popen', mock_popen_error_call)
         self.check_call_swap = self.swap(
             subprocess, 'check_call', mock_check_call)
+        # pylint: enable=unused-argument
+        # pylint: enable=super-init-not-called
 
     def test_import_with_missing_packages(self):
         def mock_exists(unused_path):
             return False
         exists_swap = self.swap(os.path, 'exists', mock_exists)
-        with self.check_call_swap, exists_swap:
+        with self.Popen_swap, self.check_call_swap, exists_swap:
             from scripts import install_third_party_libs # pylint: disable=unused-variable
         self.assertEqual(
             self.commands, [
                 sys.executable, '-m', 'pip', 'install', 'pyyaml==5.1.2',
                 '--target', '../oppia_tools/pyyaml-5.1.2',
+                '--user', '--prefix=', '--system',
                 sys.executable, '-m', 'pip', 'install',
-                'future==0.17.1', '--target', 'third_party/future-0.17.1'])
+                'future==0.17.1', '--target', 'third_party/future-0.17.1',
+                '--user', '--prefix=', '--system'])