We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
[Feature Request]: Consider allowing Python version to be 3.8.x where x >= 12
### Is there an existing issue for this?

- [X] I have searched the existing issues

### A clear and concise description of what you want to happen.

In https://github.com/oppia/oppia/issues/16436, @sagangwee noted that he couldn't get Python 3.8.12 to work on his machine, but Python 3.8.13 worked fine. We might want to consider allowing 3.8.13 etc. in the start script checks for local developers.

### Describe the solution you'd like

Consider expanding the start.py script checks to include later versions of Python that are still on the 3.8.x series.

### Describe alternatives you've considered

We could leave the existing behaviour as-is, but this poses serious problems for developers who can't install 3.8.12. 

### Additional context

_No response_

--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `scripts/setup.py`
Content:
```
1 # Copyright 2019 The Oppia Authors. All Rights Reserved.
2 #
3 # Licensed under the Apache License, Version 2.0 (the 'License');
4 # you may not use this file except in compliance with the License.
5 # You may obtain a copy of the License at
6 #
7 #      http://www.apache.org/licenses/LICENSE-2.0
8 #
9 # Unless required by applicable law or agreed to in writing, software
10 # distributed under the License is distributed on an 'AS-IS' BASIS,
11 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 # See the License for the specific language governing permissions and
13 # limitations under the License.
14 
15 """Python execution environent set up for all scripts."""
16 
17 from __future__ import annotations
18 
19 import argparse
20 import os
21 import subprocess
22 import sys
23 import tarfile
24 
25 from typing import Final, List, Optional
26 
27 from . import clean
28 from . import common
29 
30 _PARSER: Final = argparse.ArgumentParser(
31     description="""
32 Python execution environent set up for all scripts.
33 """)
34 
35 
36 def create_directory(directory_path: str) -> None:
37     """Creates a new directory. Does not do anything if directory already
38     exists.
39 
40     Args:
41         directory_path: str. Directory path to be created.
42     """
43     if os.path.exists(directory_path):
44         return
45     os.makedirs(directory_path)
46 
47 
48 # This function takes a command for python as its only input.
49 # It checks this input for a specific version of python and returns false
50 # if it does not match the expected prefix.
51 def test_python_version() -> None:
52     running_python_version = '{0[0]}.{0[1]}.{0[2]}'.format(sys.version_info)
53     if running_python_version != '3.8.12':
54         print('Please use Python 3.8.12. Exiting...')
55         # If OS is Windows, print helpful error message about adding Python to
56         # path.
57         if common.is_windows_os():
58             common.print_each_string_after_two_new_lines([
59                 'It looks like you are using Windows. If you have Python '
60                 'installed,',
61                 'make sure it is in your PATH and that PYTHONPATH is set.',
62                 'If you have two versions of Python (ie, Python 2.7 and 3), '
63                 'specify 2.7 before other versions of Python when setting the '
64                 'PATH.',
65                 'Here are some helpful articles:',
66                 'http://docs.python-guide.org/en/latest/starting/install/win/',
67                 'https://stackoverflow.com/questions/3701646/how-to-add-to-the-'
68                 'pythonpath-in-windows-7'])
69         # Exit when no suitable Python environment can be found.
70         raise Exception('No suitable python version found.')
71 
72     # Verify that Python 2 is available. Python 2 is needed for the
73     # app_devserver. See the Google Cloud docs:
74     # https://cloud.google.com/appengine/docs/standard/python3/testing-and-deploying-your-app#local-dev-server
75     return_code = subprocess.call(
76         'python2 -V', stderr=subprocess.DEVNULL, shell=True
77     )
78     if return_code != 0:
79         print(
80             '\033[91m'
81             'The Oppia server needs Python 2 to be installed. '
82             'Please follow the instructions at '
83             'https://github.com/oppia/oppia/wiki/Troubleshooting#'
84             'python-2-is-not-available to fix this.'
85             '\033[0m'
86         )
87         sys.exit(1)
88 
89 
90 def download_and_install_package(url_to_retrieve: str, filename: str) -> None:
91     """Downloads and installs package in Oppia tools directory.
92 
93     Args:
94         url_to_retrieve: string. The url from which package is to be
95             downloaded.
96         filename: string. The name of the tar file.
97     """
98     common.url_retrieve(url_to_retrieve, filename)
99     tar = tarfile.open(name=filename)
100     tar.extractall(path=common.OPPIA_TOOLS_DIR)
101     tar.close()
102     rename_yarn_folder(filename, common.OPPIA_TOOLS_DIR)
103     os.remove(filename)
104 
105 
106 def rename_yarn_folder(filename: str, path: str) -> None:
107     """Removes the `v` from the yarn folder name.
108 
109     Args:
110         filename: string. The name of the tar file.
111         path: string. The path of the yarn file.
112     """
113     if 'yarn' in filename:
114         old_name = filename.split('.tar.gz')[0]
115         new_name = ''.join(old_name.split('v'))
116         os.rename(path + '/' + old_name, path + '/' + new_name)
117 
118 
119 def download_and_install_node() -> None:
120     """Download and install node to Oppia tools directory."""
121     outfile_name = 'node-download'
122 
123     if common.is_windows_os():
124         if common.is_x64_architecture():
125             architecture = 'x64'
126         else:
127             architecture = 'x86'
128 
129         extension = '.zip'
130         node_file_name = 'node-v%s-win-%s' % (
131             common.NODE_VERSION, architecture)
132         url_to_retrieve = 'https://nodejs.org/dist/v%s/%s%s' % (
133             common.NODE_VERSION, node_file_name, extension)
134         common.url_retrieve(url_to_retrieve, outfile_name)
135         subprocess.check_call(
136             ['powershell.exe', '-c', 'expand-archive',
137              outfile_name, '-DestinationPath',
138              common.OPPIA_TOOLS_DIR])
139     else:
140         extension = '.tar.gz'
141         if common.is_x64_architecture():
142             if common.is_mac_os():
143                 node_file_name = 'node-v%s-darwin-x64' % (common.NODE_VERSION)
144             elif common.is_linux_os():
145                 node_file_name = 'node-v%s-linux-x64' % (common.NODE_VERSION)
146             # Oppia only suppports windows, mac and linux operating systems.
147             else:
148                 raise Exception(
149                     'System\'s Operating System is not compatible.')
150         else:
151             node_file_name = 'node-v%s' % common.NODE_VERSION
152         download_and_install_package(
153             'https://nodejs.org/dist/v%s/%s%s' % (
154                 common.NODE_VERSION, node_file_name, extension),
155             outfile_name)
156     os.rename(
157         os.path.join(common.OPPIA_TOOLS_DIR, node_file_name),
158         common.NODE_PATH)
159     if node_file_name == 'node-v%s' % common.NODE_VERSION:
160         with common.CD(common.NODE_PATH):
161             subprocess.check_call(['./configure'])
162             subprocess.check_call(['make'])
163 
164 
165 def main(args: Optional[List[str]] = None) -> None:
166     """Runs the script to setup Oppia."""
167     unused_parsed_args = _PARSER.parse_args(args=args)
168     test_python_version()
169 
170     # The second option allows this script to also be run from deployment
171     # folders.
172     if not os.getcwd().endswith(('oppia', 'deploy-')):
173         print('')
174         print('WARNING This script should be run from the oppia/ root folder.')
175         print('')
176         raise Exception('Invalid root directory.')
177 
178     # Set COMMON_DIR to the absolute path of the directory above OPPIA_DIR. This
179     # is necessary becaue COMMON_DIR (or subsequent variables which refer to it)
180     # may use it in a situation where relative paths won't work as expected(such
181     # as $PYTHONPATH).
182     create_directory(common.OPPIA_TOOLS_DIR)
183     create_directory(common.THIRD_PARTY_DIR)
184     common.create_readme(
185         common.THIRD_PARTY_DIR,
186         'This folder contains third party libraries used in Oppia codebase.\n'
187         'You can regenerate this folder by deleting it and then running '
188         'the start.py script.\n')
189     create_directory(common.NODE_MODULES_PATH)
190     common.create_readme(
191         common.NODE_MODULES_PATH,
192         'This folder contains node utilities used in Oppia codebase.\n'
193         'You can regenerate this folder by deleting it and then running '
194         'the start.py script.\n')
195 
196     # Download and install node.js.
197     print('Checking if node.js is installed in %s' % common.OPPIA_TOOLS_DIR)
198     if not os.path.exists(common.NODE_PATH):
199         print('Installing Node.js')
200         download_and_install_node()
201     # Change ownership of node_modules.
202     # Note: on some machines, these commands seem to take quite a long time.
203     if not common.is_windows_os():
204         common.recursive_chown(common.NODE_MODULES_PATH, os.getuid(), -1)
205         common.recursive_chmod(common.NODE_MODULES_PATH, 0o744)
206 
207     # Download and install yarn.
208     print('Checking if yarn is installed in %s' % common.OPPIA_TOOLS_DIR)
209     if not os.path.exists(common.YARN_PATH):
210         print('Removing package-lock.json')
211         clean.delete_file('package-lock.json')
212         common.print_each_string_after_two_new_lines([
213             'Installing yarn',
214             'WARNING: Please note that Oppia uses Yarn to manage node packages',
215             'do *NOT* use npm. For more information on how to use yarn,',
216             'visit https://yarnpkg.com/en/docs/usage.'])
217 
218         # NB: Update .yarnrc if the yarn version below is changed.
219         yarn_file_name = 'yarn-v%s.tar.gz' % common.YARN_VERSION
220         download_and_install_package(
221             'https://github.com/yarnpkg/yarn/releases/download/v%s/%s'
222             % (common.YARN_VERSION, yarn_file_name), yarn_file_name)
223 
224     print('Environment setup completed.')
225 
226 
227 # The 'no coverage' pragma is used as this line is un-testable. This is because
228 # it will only be called when setup.py is used as a script.
229 if __name__ == '__main__': # pragma: no cover
230     main()
231
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/scripts/setup.py b/scripts/setup.py
--- a/scripts/setup.py
+++ b/scripts/setup.py
@@ -50,8 +50,8 @@
 # if it does not match the expected prefix.
 def test_python_version() -> None:
     running_python_version = '{0[0]}.{0[1]}.{0[2]}'.format(sys.version_info)
-    if running_python_version != '3.8.12':
-        print('Please use Python 3.8.12. Exiting...')
+    if running_python_version != '3.8.15':
+        print('Please use Python 3.8.15. Exiting...')
         # If OS is Windows, print helpful error message about adding Python to
         # path.
         if common.is_windows_os():

{"golden_diff": "diff --git a/scripts/setup.py b/scripts/setup.py\n--- a/scripts/setup.py\n+++ b/scripts/setup.py\n@@ -50,8 +50,8 @@\n # if it does not match the expected prefix.\n def test_python_version() -> None:\n     running_python_version = '{0[0]}.{0[1]}.{0[2]}'.format(sys.version_info)\n-    if running_python_version != '3.8.12':\n-        print('Please use Python 3.8.12. Exiting...')\n+    if running_python_version != '3.8.15':\n+        print('Please use Python 3.8.15. Exiting...')\n         # If OS is Windows, print helpful error message about adding Python to\n         # path.\n         if common.is_windows_os():\n", "issue": "[Feature Request]: Consider allowing Python version to be 3.8.x where x >= 12\n### Is there an existing issue for this?\n\n- [X] I have searched the existing issues\n\n### A clear and concise description of what you want to happen.\n\nIn https://github.com/oppia/oppia/issues/16436, @sagangwee noted that he couldn't get Python 3.8.12 to work on his machine, but Python 3.8.13 worked fine. We might want to consider allowing 3.8.13 etc. in the start script checks for local developers.\n\n### Describe the solution you'd like\n\nConsider expanding the start.py script checks to include later versions of Python that are still on the 3.8.x series.\n\n### Describe alternatives you've considered\n\nWe could leave the existing behaviour as-is, but this poses serious problems for developers who can't install 3.8.12. \n\n### Additional context\n\n_No response_\n", "before_files": [{"content": "# Copyright 2019 The Oppia Authors. All Rights Reserved.\n#\n# Licensed under the Apache License, Version 2.0 (the 'License');\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#      http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an 'AS-IS' BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\n\"\"\"Python execution environent set up for all scripts.\"\"\"\n\nfrom __future__ import annotations\n\nimport argparse\nimport os\nimport subprocess\nimport sys\nimport tarfile\n\nfrom typing import Final, List, Optional\n\nfrom . import clean\nfrom . import common\n\n_PARSER: Final = argparse.ArgumentParser(\n    description=\"\"\"\nPython execution environent set up for all scripts.\n\"\"\")\n\n\ndef create_directory(directory_path: str) -> None:\n    \"\"\"Creates a new directory. Does not do anything if directory already\n    exists.\n\n    Args:\n        directory_path: str. Directory path to be created.\n    \"\"\"\n    if os.path.exists(directory_path):\n        return\n    os.makedirs(directory_path)\n\n\n# This function takes a command for python as its only input.\n# It checks this input for a specific version of python and returns false\n# if it does not match the expected prefix.\ndef test_python_version() -> None:\n    running_python_version = '{0[0]}.{0[1]}.{0[2]}'.format(sys.version_info)\n    if running_python_version != '3.8.12':\n        print('Please use Python 3.8.12. Exiting...')\n        # If OS is Windows, print helpful error message about adding Python to\n        # path.\n        if common.is_windows_os():\n            common.print_each_string_after_two_new_lines([\n                'It looks like you are using Windows. If you have Python '\n                'installed,',\n                'make sure it is in your PATH and that PYTHONPATH is set.',\n                'If you have two versions of Python (ie, Python 2.7 and 3), '\n                'specify 2.7 before other versions of Python when setting the '\n                'PATH.',\n                'Here are some helpful articles:',\n                'http://docs.python-guide.org/en/latest/starting/install/win/',\n                'https://stackoverflow.com/questions/3701646/how-to-add-to-the-'\n                'pythonpath-in-windows-7'])\n        # Exit when no suitable Python environment can be found.\n        raise Exception('No suitable python version found.')\n\n    # Verify that Python 2 is available. Python 2 is needed for the\n    # app_devserver. See the Google Cloud docs:\n    # https://cloud.google.com/appengine/docs/standard/python3/testing-and-deploying-your-app#local-dev-server\n    return_code = subprocess.call(\n        'python2 -V', stderr=subprocess.DEVNULL, shell=True\n    )\n    if return_code != 0:\n        print(\n            '\\033[91m'\n            'The Oppia server needs Python 2 to be installed. '\n            'Please follow the instructions at '\n            'https://github.com/oppia/oppia/wiki/Troubleshooting#'\n            'python-2-is-not-available to fix this.'\n            '\\033[0m'\n        )\n        sys.exit(1)\n\n\ndef download_and_install_package(url_to_retrieve: str, filename: str) -> None:\n    \"\"\"Downloads and installs package in Oppia tools directory.\n\n    Args:\n        url_to_retrieve: string. The url from which package is to be\n            downloaded.\n        filename: string. The name of the tar file.\n    \"\"\"\n    common.url_retrieve(url_to_retrieve, filename)\n    tar = tarfile.open(name=filename)\n    tar.extractall(path=common.OPPIA_TOOLS_DIR)\n    tar.close()\n    rename_yarn_folder(filename, common.OPPIA_TOOLS_DIR)\n    os.remove(filename)\n\n\ndef rename_yarn_folder(filename: str, path: str) -> None:\n    \"\"\"Removes the `v` from the yarn folder name.\n\n    Args:\n        filename: string. The name of the tar file.\n        path: string. The path of the yarn file.\n    \"\"\"\n    if 'yarn' in filename:\n        old_name = filename.split('.tar.gz')[0]\n        new_name = ''.join(old_name.split('v'))\n        os.rename(path + '/' + old_name, path + '/' + new_name)\n\n\ndef download_and_install_node() -> None:\n    \"\"\"Download and install node to Oppia tools directory.\"\"\"\n    outfile_name = 'node-download'\n\n    if common.is_windows_os():\n        if common.is_x64_architecture():\n            architecture = 'x64'\n        else:\n            architecture = 'x86'\n\n        extension = '.zip'\n        node_file_name = 'node-v%s-win-%s' % (\n            common.NODE_VERSION, architecture)\n        url_to_retrieve = 'https://nodejs.org/dist/v%s/%s%s' % (\n            common.NODE_VERSION, node_file_name, extension)\n        common.url_retrieve(url_to_retrieve, outfile_name)\n        subprocess.check_call(\n            ['powershell.exe', '-c', 'expand-archive',\n             outfile_name, '-DestinationPath',\n             common.OPPIA_TOOLS_DIR])\n    else:\n        extension = '.tar.gz'\n        if common.is_x64_architecture():\n            if common.is_mac_os():\n                node_file_name = 'node-v%s-darwin-x64' % (common.NODE_VERSION)\n            elif common.is_linux_os():\n                node_file_name = 'node-v%s-linux-x64' % (common.NODE_VERSION)\n            # Oppia only suppports windows, mac and linux operating systems.\n            else:\n                raise Exception(\n                    'System\\'s Operating System is not compatible.')\n        else:\n            node_file_name = 'node-v%s' % common.NODE_VERSION\n        download_and_install_package(\n            'https://nodejs.org/dist/v%s/%s%s' % (\n                common.NODE_VERSION, node_file_name, extension),\n            outfile_name)\n    os.rename(\n        os.path.join(common.OPPIA_TOOLS_DIR, node_file_name),\n        common.NODE_PATH)\n    if node_file_name == 'node-v%s' % common.NODE_VERSION:\n        with common.CD(common.NODE_PATH):\n            subprocess.check_call(['./configure'])\n            subprocess.check_call(['make'])\n\n\ndef main(args: Optional[List[str]] = None) -> None:\n    \"\"\"Runs the script to setup Oppia.\"\"\"\n    unused_parsed_args = _PARSER.parse_args(args=args)\n    test_python_version()\n\n    # The second option allows this script to also be run from deployment\n    # folders.\n    if not os.getcwd().endswith(('oppia', 'deploy-')):\n        print('')\n        print('WARNING This script should be run from the oppia/ root folder.')\n        print('')\n        raise Exception('Invalid root directory.')\n\n    # Set COMMON_DIR to the absolute path of the directory above OPPIA_DIR. This\n    # is necessary becaue COMMON_DIR (or subsequent variables which refer to it)\n    # may use it in a situation where relative paths won't work as expected(such\n    # as $PYTHONPATH).\n    create_directory(common.OPPIA_TOOLS_DIR)\n    create_directory(common.THIRD_PARTY_DIR)\n    common.create_readme(\n        common.THIRD_PARTY_DIR,\n        'This folder contains third party libraries used in Oppia codebase.\\n'\n        'You can regenerate this folder by deleting it and then running '\n        'the start.py script.\\n')\n    create_directory(common.NODE_MODULES_PATH)\n    common.create_readme(\n        common.NODE_MODULES_PATH,\n        'This folder contains node utilities used in Oppia codebase.\\n'\n        'You can regenerate this folder by deleting it and then running '\n        'the start.py script.\\n')\n\n    # Download and install node.js.\n    print('Checking if node.js is installed in %s' % common.OPPIA_TOOLS_DIR)\n    if not os.path.exists(common.NODE_PATH):\n        print('Installing Node.js')\n        download_and_install_node()\n    # Change ownership of node_modules.\n    # Note: on some machines, these commands seem to take quite a long time.\n    if not common.is_windows_os():\n        common.recursive_chown(common.NODE_MODULES_PATH, os.getuid(), -1)\n        common.recursive_chmod(common.NODE_MODULES_PATH, 0o744)\n\n    # Download and install yarn.\n    print('Checking if yarn is installed in %s' % common.OPPIA_TOOLS_DIR)\n    if not os.path.exists(common.YARN_PATH):\n        print('Removing package-lock.json')\n        clean.delete_file('package-lock.json')\n        common.print_each_string_after_two_new_lines([\n            'Installing yarn',\n            'WARNING: Please note that Oppia uses Yarn to manage node packages',\n            'do *NOT* use npm. For more information on how to use yarn,',\n            'visit https://yarnpkg.com/en/docs/usage.'])\n\n        # NB: Update .yarnrc if the yarn version below is changed.\n        yarn_file_name = 'yarn-v%s.tar.gz' % common.YARN_VERSION\n        download_and_install_package(\n            'https://github.com/yarnpkg/yarn/releases/download/v%s/%s'\n            % (common.YARN_VERSION, yarn_file_name), yarn_file_name)\n\n    print('Environment setup completed.')\n\n\n# The 'no coverage' pragma is used as this line is un-testable. This is because\n# it will only be called when setup.py is used as a script.\nif __name__ == '__main__': # pragma: no cover\n    main()\n", "path": "scripts/setup.py"}], "after_files": [{"content": "# Copyright 2019 The Oppia Authors. All Rights Reserved.\n#\n# Licensed under the Apache License, Version 2.0 (the 'License');\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#      http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an 'AS-IS' BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\n\"\"\"Python execution environent set up for all scripts.\"\"\"\n\nfrom __future__ import annotations\n\nimport argparse\nimport os\nimport subprocess\nimport sys\nimport tarfile\n\nfrom typing import Final, List, Optional\n\nfrom . import clean\nfrom . import common\n\n_PARSER: Final = argparse.ArgumentParser(\n    description=\"\"\"\nPython execution environent set up for all scripts.\n\"\"\")\n\n\ndef create_directory(directory_path: str) -> None:\n    \"\"\"Creates a new directory. Does not do anything if directory already\n    exists.\n\n    Args:\n        directory_path: str. Directory path to be created.\n    \"\"\"\n    if os.path.exists(directory_path):\n        return\n    os.makedirs(directory_path)\n\n\n# This function takes a command for python as its only input.\n# It checks this input for a specific version of python and returns false\n# if it does not match the expected prefix.\ndef test_python_version() -> None:\n    running_python_version = '{0[0]}.{0[1]}.{0[2]}'.format(sys.version_info)\n    if running_python_version != '3.8.15':\n        print('Please use Python 3.8.15. Exiting...')\n        # If OS is Windows, print helpful error message about adding Python to\n        # path.\n        if common.is_windows_os():\n            common.print_each_string_after_two_new_lines([\n                'It looks like you are using Windows. If you have Python '\n                'installed,',\n                'make sure it is in your PATH and that PYTHONPATH is set.',\n                'If you have two versions of Python (ie, Python 2.7 and 3), '\n                'specify 2.7 before other versions of Python when setting the '\n                'PATH.',\n                'Here are some helpful articles:',\n                'http://docs.python-guide.org/en/latest/starting/install/win/',\n                'https://stackoverflow.com/questions/3701646/how-to-add-to-the-'\n                'pythonpath-in-windows-7'])\n        # Exit when no suitable Python environment can be found.\n        raise Exception('No suitable python version found.')\n\n    # Verify that Python 2 is available. Python 2 is needed for the\n    # app_devserver. See the Google Cloud docs:\n    # https://cloud.google.com/appengine/docs/standard/python3/testing-and-deploying-your-app#local-dev-server\n    return_code = subprocess.call(\n        'python2 -V', stderr=subprocess.DEVNULL, shell=True\n    )\n    if return_code != 0:\n        print(\n            '\\033[91m'\n            'The Oppia server needs Python 2 to be installed. '\n            'Please follow the instructions at '\n            'https://github.com/oppia/oppia/wiki/Troubleshooting#'\n            'python-2-is-not-available to fix this.'\n            '\\033[0m'\n        )\n        sys.exit(1)\n\n\ndef download_and_install_package(url_to_retrieve: str, filename: str) -> None:\n    \"\"\"Downloads and installs package in Oppia tools directory.\n\n    Args:\n        url_to_retrieve: string. The url from which package is to be\n            downloaded.\n        filename: string. The name of the tar file.\n    \"\"\"\n    common.url_retrieve(url_to_retrieve, filename)\n    tar = tarfile.open(name=filename)\n    tar.extractall(path=common.OPPIA_TOOLS_DIR)\n    tar.close()\n    rename_yarn_folder(filename, common.OPPIA_TOOLS_DIR)\n    os.remove(filename)\n\n\ndef rename_yarn_folder(filename: str, path: str) -> None:\n    \"\"\"Removes the `v` from the yarn folder name.\n\n    Args:\n        filename: string. The name of the tar file.\n        path: string. The path of the yarn file.\n    \"\"\"\n    if 'yarn' in filename:\n        old_name = filename.split('.tar.gz')[0]\n        new_name = ''.join(old_name.split('v'))\n        os.rename(path + '/' + old_name, path + '/' + new_name)\n\n\ndef download_and_install_node() -> None:\n    \"\"\"Download and install node to Oppia tools directory.\"\"\"\n    outfile_name = 'node-download'\n\n    if common.is_windows_os():\n        if common.is_x64_architecture():\n            architecture = 'x64'\n        else:\n            architecture = 'x86'\n\n        extension = '.zip'\n        node_file_name = 'node-v%s-win-%s' % (\n            common.NODE_VERSION, architecture)\n        url_to_retrieve = 'https://nodejs.org/dist/v%s/%s%s' % (\n            common.NODE_VERSION, node_file_name, extension)\n        common.url_retrieve(url_to_retrieve, outfile_name)\n        subprocess.check_call(\n            ['powershell.exe', '-c', 'expand-archive',\n             outfile_name, '-DestinationPath',\n             common.OPPIA_TOOLS_DIR])\n    else:\n        extension = '.tar.gz'\n        if common.is_x64_architecture():\n            if common.is_mac_os():\n                node_file_name = 'node-v%s-darwin-x64' % (common.NODE_VERSION)\n            elif common.is_linux_os():\n                node_file_name = 'node-v%s-linux-x64' % (common.NODE_VERSION)\n            # Oppia only suppports windows, mac and linux operating systems.\n            else:\n                raise Exception(\n                    'System\\'s Operating System is not compatible.')\n        else:\n            node_file_name = 'node-v%s' % common.NODE_VERSION\n        download_and_install_package(\n            'https://nodejs.org/dist/v%s/%s%s' % (\n                common.NODE_VERSION, node_file_name, extension),\n            outfile_name)\n    os.rename(\n        os.path.join(common.OPPIA_TOOLS_DIR, node_file_name),\n        common.NODE_PATH)\n    if node_file_name == 'node-v%s' % common.NODE_VERSION:\n        with common.CD(common.NODE_PATH):\n            subprocess.check_call(['./configure'])\n            subprocess.check_call(['make'])\n\n\ndef main(args: Optional[List[str]] = None) -> None:\n    \"\"\"Runs the script to setup Oppia.\"\"\"\n    unused_parsed_args = _PARSER.parse_args(args=args)\n    test_python_version()\n\n    # The second option allows this script to also be run from deployment\n    # folders.\n    if not os.getcwd().endswith(('oppia', 'deploy-')):\n        print('')\n        print('WARNING This script should be run from the oppia/ root folder.')\n        print('')\n        raise Exception('Invalid root directory.')\n\n    # Set COMMON_DIR to the absolute path of the directory above OPPIA_DIR. This\n    # is necessary becaue COMMON_DIR (or subsequent variables which refer to it)\n    # may use it in a situation where relative paths won't work as expected(such\n    # as $PYTHONPATH).\n    create_directory(common.OPPIA_TOOLS_DIR)\n    create_directory(common.THIRD_PARTY_DIR)\n    common.create_readme(\n        common.THIRD_PARTY_DIR,\n        'This folder contains third party libraries used in Oppia codebase.\\n'\n        'You can regenerate this folder by deleting it and then running '\n        'the start.py script.\\n')\n    create_directory(common.NODE_MODULES_PATH)\n    common.create_readme(\n        common.NODE_MODULES_PATH,\n        'This folder contains node utilities used in Oppia codebase.\\n'\n        'You can regenerate this folder by deleting it and then running '\n        'the start.py script.\\n')\n\n    # Download and install node.js.\n    print('Checking if node.js is installed in %s' % common.OPPIA_TOOLS_DIR)\n    if not os.path.exists(common.NODE_PATH):\n        print('Installing Node.js')\n        download_and_install_node()\n    # Change ownership of node_modules.\n    # Note: on some machines, these commands seem to take quite a long time.\n    if not common.is_windows_os():\n        common.recursive_chown(common.NODE_MODULES_PATH, os.getuid(), -1)\n        common.recursive_chmod(common.NODE_MODULES_PATH, 0o744)\n\n    # Download and install yarn.\n    print('Checking if yarn is installed in %s' % common.OPPIA_TOOLS_DIR)\n    if not os.path.exists(common.YARN_PATH):\n        print('Removing package-lock.json')\n        clean.delete_file('package-lock.json')\n        common.print_each_string_after_two_new_lines([\n            'Installing yarn',\n            'WARNING: Please note that Oppia uses Yarn to manage node packages',\n            'do *NOT* use npm. For more information on how to use yarn,',\n            'visit https://yarnpkg.com/en/docs/usage.'])\n\n        # NB: Update .yarnrc if the yarn version below is changed.\n        yarn_file_name = 'yarn-v%s.tar.gz' % common.YARN_VERSION\n        download_and_install_package(\n            'https://github.com/yarnpkg/yarn/releases/download/v%s/%s'\n            % (common.YARN_VERSION, yarn_file_name), yarn_file_name)\n\n    print('Environment setup completed.')\n\n\n# The 'no coverage' pragma is used as this line is un-testable. This is because\n# it will only be called when setup.py is used as a script.\nif __name__ == '__main__': # pragma: no cover\n    main()\n", "path": "scripts/setup.py"}]}