Merge pull request #798 from seleniumbase/add-sbase-mkpres-script

Add the "sbase mkpres" console script for making HTML presentations
This commit is contained in:
Michael Mintz 2021-01-28 22:59:56 -05:00 committed by GitHub
commit 4d3d43e4e2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 408 additions and 64 deletions

110
README.md
View File

@ -112,7 +112,8 @@ pip install seleniumbase
COMMANDS:
install [DRIVER] [OPTIONS]
mkdir [DIRECTORY]
mkfile [FILE.py]
mkfile [FILE.py] [OPTIONS]
mkpres [FILE.py] [LANGUAGE OPTIONS]
options (List common pytest options)
print [FILE] [OPTIONS]
translate [SB_FILE.py] [LANG] [ACTION]
@ -152,53 +153,24 @@ sbase install chromedriver latest
(See [seleniumbase.io/seleniumbase/console_scripts/ReadMe/](https://seleniumbase.io/seleniumbase/console_scripts/ReadMe/) for more information on SeleniumBase console scripts.)
<h3><img src="https://seleniumbase.io/img/logo6.png" title="SeleniumBase" width="32" /> Create and run tests:</h3>
<h3><img src="https://seleniumbase.io/img/logo6.png" title="SeleniumBase" width="32" /> Running tests:</h3>
🔵 ``sbase mkdir DIR`` creates a folder with sample tests:
```bash
sbase mkdir ui_tests
cd ui_tests/
```
> That folder will have the following files:
```
ui_tests/
├── __init__.py
├── boilerplates/
│ ├── __init__.py
│ ├── base_test_case.py
│ ├── boilerplate_test.py
│ ├── page_objects.py
│ └── samples/
│ ├── __init__.py
│ ├── google_objects.py
│ └── google_test.py
├── my_first_test.py
├── parameterized_test.py
├── pytest.ini
├── requirements.txt
├── setup.cfg
└── test_demo_site.py
```
🔵 <b>Run a sample test with ``pytest``:</b>
```bash
pytest test_demo_site.py
```
> (Chrome is the default browser if not specified with ``--browser=BROWSER``.)
> (On Linux, ``--headless`` is the default behavior. You can also run in headless mode on any OS. If your Linux machine has a GUI and you want to see the web browser as tests run, add ``--headed`` or ``--gui``.)
<a href="https://github.com/seleniumbase/SeleniumBase/blob/master/examples/test_demo_site.py"><img src="https://seleniumbase.io/cdn/gif/demo_page_1.gif" title="SeleniumBase Demo Page" /></a><br />
🔵 <b>If you've cloned SeleniumBase from GitHub, you can run sample tests from the [SeleniumBase/examples/](https://github.com/seleniumbase/SeleniumBase/tree/master/examples) folder:</b>
🔵 <b>If you've cloned SeleniumBase from GitHub, you can run sample tests from the [examples/](https://github.com/seleniumbase/SeleniumBase/tree/master/examples) folder:</b>
```bash
cd examples/
pytest test_demo_site.py
```
> (Chrome is the default browser if not specified with ``--browser=BROWSER``. On Linux, ``--headless`` is the default behavior. You can also run in headless mode on any OS. If your Linux machine has a GUI and you want to see the web browser as tests run, add ``--headed`` or ``--gui``.)
<a href="https://github.com/seleniumbase/SeleniumBase/blob/master/examples/test_demo_site.py"><img src="https://seleniumbase.io/cdn/gif/demo_page_1.gif" title="SeleniumBase Demo Page" /></a><br />
🔵 <b>Here are more examples that you can run:</b>
```bash
pytest my_first_test.py
pytest test_swag_labs.py
```
@ -438,18 +410,56 @@ Here's the command-line option to add to tests: (See [examples/custom_settings.p
Inside your tests, you can use ``self.data`` to access that.
<h3><img src="https://seleniumbase.io/img/logo6.png" title="SeleniumBase" width="32" /> Test Directory Customization:</h3>
<h3><img src="https://seleniumbase.io/img/logo6.png" title="SeleniumBase" width="32" /> Test Directory Configuration:</h3>
🔵 For running tests outside of the SeleniumBase repo with **pytest**, you'll want a copy of **[pytest.ini](https://github.com/seleniumbase/SeleniumBase/blob/master/pytest.ini)** on the root folder. For running tests outside of the SeleniumBase repo with **nosetests**, you'll want a copy of **[setup.cfg](https://github.com/seleniumbase/SeleniumBase/blob/master/setup.cfg)** on the root folder. (Subfolders should include a blank ``__init__.py`` file.) These files specify default configuration details for tests. (For nosetest runs, you can also specify a .cfg file by using ``--config``. Example ``nosetests [MY_TEST.py] --config=[MY_CONFIG.cfg]``)
🔵 When running tests with **pytest**, you'll want a copy of **[pytest.ini](https://github.com/seleniumbase/SeleniumBase/blob/master/pytest.ini)** in your root folders. When running tests with **nosetests**, you'll want a copy of **[setup.cfg](https://github.com/seleniumbase/SeleniumBase/blob/master/setup.cfg)** in your root folders. These files specify default configuration details for tests. Folders should also include a blank ``__init__.py`` file, which allows your tests to import files from that folder.
🔵 As a shortcut, you'll be able to run ``sbase mkdir [DIRECTORY]`` to create a new folder that already contains necessary files and some example tests that you can run.
🔵 ``sbase mkdir DIR`` creates a folder with config files and sample tests:
```bash
sbase mkdir ui_tests
cd ui_tests/
pytest test_demo_site.py
```
> That new folder will have these files:
```bash
ui_tests/
├── __init__.py
├── boilerplates/
│ ├── __init__.py
│ ├── base_test_case.py
│ ├── boilerplate_test.py
│ ├── page_objects.py
│ └── samples/
│ ├── __init__.py
│ ├── google_objects.py
│ └── google_test.py
├── my_first_test.py
├── parameterized_test.py
├── pytest.ini
├── requirements.txt
├── setup.cfg
└── test_demo_site.py
```
<b>ProTip™:</b> You can also create a boilerplate folder without any sample tests in it by adding ``-b`` or ``--basic`` to the ``sbase mkdir`` command:
```bash
sbase mkdir ui_tests --basic
```
> That new folder will have these files:
```bash
ui_tests/
├── __init__.py
├── pytest.ini
├── requirements.txt
└── setup.cfg
```
Of those files, the ``pytest.ini`` config file is the most important, followed by a blank ``__init__.py`` file. There's also a ``setup.cfg`` file (only needed for nosetests). Finally, the ``requirements.txt`` file can be used to help you install seleniumbase into your environments (if it's not already installed).
--------
<h3><img src="https://seleniumbase.io/img/logo6.png" title="SeleniumBase" width="32" /> Log files from failed tests:</h3>
@ -467,7 +477,7 @@ class MyTestClass(BaseCase):
self.assert_element("div#ARMY_OF_ROBOTS", timeout=1) # This should fail
```
You can run it from the ``examples`` folder like this:
You can run it from the ``examples/`` folder like this:
```bash
pytest test_fail.py
@ -589,7 +599,7 @@ pytest user_agent_test.py --agent="Mozilla/5.0 (Nintendo 3DS; U; ; en) Version/1
<h3><img src="https://seleniumbase.io/img/logo6.png" title="SeleniumBase" width="32" /> Building Guided Tours for Websites:</h3>
🔵 Learn about <a href="https://github.com/seleniumbase/SeleniumBase/blob/master/examples/tour_examples/ReadMe.md">SeleniumBase Interactive Walkthroughs</a> (in the ``examples/tour_examples`` folder). It's great for prototyping a website onboarding experience.
🔵 Learn about <a href="https://github.com/seleniumbase/SeleniumBase/blob/master/examples/tour_examples/ReadMe.md">SeleniumBase Interactive Walkthroughs</a> (in the ``examples/tour_examples/`` folder). It's great for prototyping a website onboarding experience.
<a id="utilizing_advanced_features"></a>

View File

@ -1,10 +1,10 @@
<h3 align="left"><img src="https://seleniumbase.io/cdn/img/sb_logo_b.png" alt="SeleniumBase" width="360" /></h3>
<h3 align="left"><img src="https://seleniumbase.io/cdn/img/sb_logo_b.png" alt="SeleniumBase" width="350" /></h3>
<h3> 📊 ChartMaker 📈 </h3>
<h2> 📊 ChartMaker 📈 </h2>
SeleniumBase ChartMaker allows you to create HTML charts with Python. (Using HighCharts JS)
<p>SeleniumBase ChartMaker lets you use Python to generate HTML charts from HighCharts JS.</p>
<a href="https://seleniumbase.io/other/chart_presentation.html"><img width="500" src="https://seleniumbase.io/other/chart_presentation.gif" title="Screenshot"></a><br>
<a href="https://seleniumbase.io/other/chart_presentation.html"><img width="480" src="https://seleniumbase.io/cdn/gif/chart_pres.gif" title="Chart Presentation"></a><br>
([Click to see a presentation with multiple charts](https://seleniumbase.io/other/chart_presentation.html))

View File

@ -2,10 +2,9 @@
<h1> 📰 Presenter 📑 </h1>
<b>SeleniumBase Presenter allows you to create HTML presentations with Python.</b><br />
(The "Reveal-JS" library is used for running the presentations.)
<p>SeleniumBase Presenter lets you use Python to generate HTML presentations from Reveal JS.</p>
**Here's a sample presentation:**
<b>Here's a sample presentation:</b>
<a href="https://seleniumbase.io/other/presenter.html"><img width="500" src="https://seleniumbase.io/other/presenter.gif" title="Screenshot"></a><br>

View File

@ -1,10 +1,10 @@
<h3 align="left"><img src="https://seleniumbase.io/cdn/img/sb_logo_b.png" alt="SeleniumBase" width="360" /></h3>
<h3 align="left"><img src="https://seleniumbase.io/cdn/img/sb_logo_b.png" alt="SeleniumBase" width="350" /></h3>
<h3> 📊 ChartMaker 📈 </h3>
<h2> 📊 ChartMaker 📈 </h2>
SeleniumBase ChartMaker allows you to create HTML charts with Python. (Using HighCharts JS)
<p>SeleniumBase ChartMaker lets you use Python to generate HTML charts from HighCharts JS.</p>
<a href="https://seleniumbase.io/other/chart_presentation.html"><img width="500" src="https://seleniumbase.io/other/chart_presentation.gif" title="Screenshot"></a><br>
<a href="https://seleniumbase.io/other/chart_presentation.html"><img width="480" src="https://seleniumbase.io/cdn/gif/chart_pres.gif" title="Chart Presentation"></a><br>
([Click to see a presentation with multiple charts](https://seleniumbase.io/other/chart_presentation.html))

View File

@ -78,7 +78,7 @@ toml==0.10.2
Pillow==6.2.2;python_version<"3.5"
Pillow==7.2.0;python_version>="3.5" and python_version<"3.6"
Pillow==8.1.0;python_version>="3.6"
rich==9.9.0;python_version>="3.6" and python_version<"4.0"
rich==9.10.0;python_version>="3.6" and python_version<"4.0"
flake8==3.7.9;python_version<"3.5"
flake8==3.8.4;python_version>="3.5"
pyflakes==2.1.1;python_version<"3.5"

View File

@ -1,2 +1,2 @@
# seleniumbase package
__version__ = "1.53.3"
__version__ = "1.54.0"

View File

@ -81,6 +81,28 @@ methods, which are "open", "type", "click",
basic boilerplate option, only the "open" method
is included.
### mkpres
* Usage:
``sbase mkpres [FILE.py] [LANGUAGE OPTIONS]``
* Example:
``sbase mkpres new_presentation.py``
* Language Options:
``--en`` / ``--English`` | ``--zh`` / ``--Chinese``
``--nl`` / ``--Dutch`` | ``--fr`` / ``--French``
``--it`` / ``--Italian`` | ``--ja`` / ``--Japanese``
``--ko`` / ``--Korean`` | ``--pt`` / ``--Portuguese``
``--ru`` / ``--Russian`` | ``--es`` / ``--Spanish``
* Output:
Creates a new presentation with 3 example slides.
If the file already exists, an error is raised.
By default, the slides are written in English.
Slides use "serif" theme & "fade" transition.
This code can be used as a base boilerplate.
### options
* Usage:

View File

@ -9,6 +9,7 @@ Examples:
sbase install chromedriver
sbase mkdir ui_tests
sbase mkfile new_test.py
sbase mkpres new_presentation.py
sbase options
sbase convert webdriver_unittest_file.py
sbase print my_first_test.py -n
@ -67,6 +68,7 @@ def show_basic_usage():
sc += (" install [DRIVER] [OPTIONS]\n")
sc += (" mkdir [DIRECTORY] [OPTIONS]\n")
sc += (" mkfile [FILE.py] [OPTIONS]\n")
sc += (" mkpres [FILE.py] [LANGUAGE OPTIONS]\n")
sc += (" options (List common pytest options)\n")
sc += (" print [FILE] [OPTIONS]\n")
sc += (" translate [SB_FILE.py] [LANG] [ACTION]\n")
@ -183,6 +185,33 @@ def show_mkfile_usage():
print("")
def show_mkpres_usage():
c2 = colorama.Fore.BLUE + colorama.Back.LIGHTGREEN_EX
c3 = colorama.Fore.BLUE + colorama.Back.LIGHTYELLOW_EX
cr = colorama.Style.RESET_ALL
sc = (" " + c2 + "** " + c3 + "mkpres" + c2 + " **" + cr)
print(sc)
print("")
print(" Usage:")
print(" seleniumbase mkpres [FILE.py] [LANGUAGE OPTIONS]")
print(" OR: sbase mkpres [FILE.py] [LANGUAGE OPTIONS]")
print(" Example:")
print(" sbase mkpres new_presentation.py")
print(" Language Options:")
print(" --en / --English | --zh / --Chinese")
print(" --nl / --Dutch | --fr / --French")
print(" --it / --Italian | --ja / --Japanese")
print(" --ko / --Korean | --pt / --Portuguese")
print(" --ru / --Russian | --es / --Spanish")
print(" Output:")
print(" Creates a new presentation with 3 example slides.")
print(" If the file already exists, an error is raised.")
print(" By default, the slides are written in English.")
print(' Slides use "serif" theme & "fade" transition.')
print(" This code can be used as a base boilerplate.")
print("")
def show_convert_usage():
c2 = colorama.Fore.BLUE + colorama.Back.LIGHTGREEN_EX
c3 = colorama.Fore.BLUE + colorama.Back.LIGHTYELLOW_EX
@ -540,6 +569,7 @@ def show_detailed_help():
show_install_usage()
show_mkdir_usage()
show_mkfile_usage()
show_mkpres_usage()
show_convert_usage()
show_print_usage()
show_translate_usage()
@ -591,6 +621,13 @@ def main():
else:
show_basic_usage()
show_mkfile_usage()
elif command == "mkpres":
if len(command_args) >= 1:
from seleniumbase.console_scripts import sb_mkpres
sb_mkpres.main()
else:
show_basic_usage()
show_mkpres_usage()
elif command == "convert":
if len(command_args) == 1:
from seleniumbase.utilities.selenium_ide import convert_ide
@ -718,6 +755,10 @@ def main():
print("")
show_mkfile_usage()
return
elif command_args[0] == "mkpres":
print("")
show_mkpres_usage()
return
elif command_args[0] == "convert":
print("")
show_convert_usage()

View File

@ -0,0 +1,272 @@
# -*- coding: utf-8 -*-
"""
Creates a new SeleniumBase presentation with boilerplate code.
Usage:
seleniumbase mkpres [FILE.py] [LANGUAGE OPTIONS]
or sbase mkpres [FILE.py] [LANGUAGE OPTIONS]
Example:
sbase mkpres new_presentation.py
Language Options:
--en / --English | --zh / --Chinese
--nl / --Dutch | --fr / --French
--it / --Italian | --ja / --Japanese
--ko / --Korean | --pt / --Portuguese
--ru / --Russian | --es / --Spanish
Output:
Creates a new presentation with 3 example slides.
If the file already exists, an error is raised.
By default, the slides are written in English.
Slides use "serif" theme & "fade" transition.
This code can be used as a base boilerplate.
"""
import codecs
import colorama
import os
import sys
def invalid_run_command(msg=None):
exp = (" ** mkpres **\n\n")
exp += " Usage:\n"
exp += " seleniumbase mkpres [FILE.py] [LANGUAGE OPTIONS]\n"
exp += " OR sbase mkpres [FILE.py] [LANGUAGE OPTIONS]\n"
exp += " Example:\n"
exp += " sbase mkpres new_presentation.py\n"
exp += " Language Options:\n"
exp += " --en / --English | --zh / --Chinese\n"
exp += " --nl / --Dutch | --fr / --French\n"
exp += " --it / --Italian | --ja / --Japanese\n"
exp += " --ko / --Korean | --pt / --Portuguese\n"
exp += " --ru / --Russian | --es / --Spanish\n"
exp += " Output:\n"
exp += ' Creates a new presentation with 3 example slides.\n'
exp += ' If the file already exists, an error is raised.\n'
exp += ' By default, the slides are written in English.\n'
exp += ' Slides use "serif" theme & "fade" transition.\n'
exp += ' This code can be used as a base boilerplate.\n'
if not msg:
raise Exception('INVALID RUN COMMAND!\n\n%s' % exp)
elif msg == "help":
print("\n%s" % exp)
sys.exit()
else:
raise Exception('INVALID RUN COMMAND!\n\n%s\n%s\n' % (exp, msg))
def main():
c1 = ""
c5 = ""
c7 = ""
cr = ""
if "linux" not in sys.platform:
colorama.init(autoreset=True)
c1 = colorama.Fore.BLUE + colorama.Back.LIGHTCYAN_EX
c5 = colorama.Fore.RED + colorama.Back.LIGHTYELLOW_EX
c7 = colorama.Fore.BLACK + colorama.Back.MAGENTA
cr = colorama.Style.RESET_ALL
help_me = False
error_msg = None
invalid_cmd = None
language = "English"
command_args = sys.argv[2:]
file_name = command_args[0]
if file_name == "-h" or file_name == "--help":
invalid_run_command("help")
elif not file_name.endswith(".py"):
error_msg = 'File name must end with ".py"!'
elif "*" in file_name or len(str(file_name)) < 4:
error_msg = 'Invalid file name!'
elif file_name.startswith("-"):
error_msg = 'File name cannot start with "-"!'
elif "/" in str(file_name) or "\\" in str(file_name):
error_msg = 'File must be created in the current directory!'
elif os.path.exists(os.getcwd() + '/' + file_name):
error_msg = (
'File "%s" already exists in this directory!' % file_name)
if error_msg:
error_msg = c5 + "ERROR: " + error_msg + cr
invalid_run_command(error_msg)
if len(command_args) >= 2:
options = command_args[1:]
for option in options:
option = option.lower()
if option == "-h" or option == "--help":
help_me = True
elif option == "--en" or option == "--english":
language = "English"
elif option == "--zh" or option == "--chinese":
language = "Chinese"
elif option == "--nl" or option == "--dutch":
language = "Dutch"
elif option == "--fr" or option == "--french":
language = "French"
elif option == "--it" or option == "--italian":
language = "Italian"
elif option == "--ja" or option == "--japanese":
language = "Japanese"
elif option == "--ko" or option == "--korean":
language = "Korean"
elif option == "--pt" or option == "--portuguese":
language = "Portuguese"
elif option == "--ru" or option == "--russian":
language = "Russian"
elif option == "--es" or option == "--spanish":
language = "Spanish"
else:
invalid_cmd = "\n===> INVALID OPTION: >> %s <<\n" % option
invalid_cmd = invalid_cmd.replace('>> ', ">>" + c5 + " ")
invalid_cmd = invalid_cmd.replace(' <<', " " + cr + "<<")
invalid_cmd = invalid_cmd.replace('>>', c7 + ">>" + cr)
invalid_cmd = invalid_cmd.replace('<<', c7 + "<<" + cr)
help_me = True
break
if help_me:
invalid_run_command(invalid_cmd)
if language != "English" and sys.version_info[0] == 2:
print("")
msg = 'Multi-language support for "sbase mkpres" '
msg += 'is not available on Python 2!'
msg = "\n" + c5 + msg + cr
msg += '\nPlease run in "English" mode or upgrade to Python 3!\n'
raise Exception(msg)
dir_name = os.getcwd()
file_path = "%s/%s" % (dir_name, file_name)
html_name = file_name.replace(".py", ".html")
hello = "Hello"
update_text = "Update Text"
goodbye = "Goodbye"
class_name = "MyTestClass"
if language == "Chinese":
hello = "你好"
update_text = "更新文本"
goodbye = "再见"
class_name = "我的测试类"
elif language == "Dutch":
hello = "Hallo"
update_text = "Tekst Bijwerken"
goodbye = "Dag"
class_name = "MijnTestklasse"
elif language == "French":
hello = "Bonjour"
update_text = "Modifier Texte"
goodbye = "Au revoir"
class_name = "MaClasseDeTest"
elif language == "Italian":
hello = "Ciao"
update_text = "Aggiornare Testo"
goodbye = "Addio"
class_name = "MiaClasseDiTest"
elif language == "Japanese":
hello = "こんにちは"
update_text = "テキストを更新"
goodbye = "さようなら"
class_name = "私のテストクラス"
elif language == "Korean":
hello = "여보세요"
update_text = "텍스트를 업데이트"
goodbye = "안녕"
class_name = "테스트_클래스"
elif language == "Portuguese":
hello = "Olá"
update_text = "Atualizar Texto"
goodbye = "Tchau"
class_name = "MinhaClasseDeTeste"
elif language == "Russian":
hello = "Привет"
update_text = "обновить текст"
goodbye = "До свидания"
class_name = "МойТестовыйКласс"
elif language == "Spanish":
hello = "Hola"
update_text = "Actualizar Texto"
goodbye = "Adiós"
class_name = "MiClaseDePrueba"
import_line = "from seleniumbase import BaseCase"
parent_class = "BaseCase"
class_line = "class MyTestClass(BaseCase):"
if language != "English":
from seleniumbase.translate.master_dict import MD_F
import_line = MD_F.get_import_line(language)
parent_class = MD_F.get_lang_parent_class(language)
class_line = "class %s(%s):" % (class_name, parent_class)
settings = 'theme="serif", transition="fade"'
img_src = 'src="https://seleniumbase.io/cdn/img/sb6.png"'
hello_page = (
"\n '<h1>%s</h1><br />'"
"\n '<img %s>'"
'' % (hello, img_src))
update_text_page = "<h2><b>*</b> %s <b>*</b></h2>" % update_text
goodbye_page = "<h2>%s</h2><p>Use SeleniumBase!</p>" % goodbye
data = []
data.append("%s" % import_line)
data.append("")
data.append("")
data.append("%s" % class_line)
data.append("")
data.append(" def test_presentation(self):")
data.append(' self.create_presentation(%s)' % settings)
data.append(' self.add_slide(%s)' % hello_page)
data.append(' self.add_slide("%s")' % update_text_page)
data.append(' self.add_slide("%s")' % goodbye_page)
data.append(' self.begin_presentation(filename="%s")' % html_name)
data.append("")
new_data = []
if language == "English":
new_data = data
else:
from seleniumbase.translate.master_dict import MD
from seleniumbase.translate.master_dict import MD_L_Codes
md = MD.md
lang_codes = MD_L_Codes.lang
nl_code = lang_codes[language]
dl_code = lang_codes["English"]
for line in data:
found_swap = False
replace_count = line.count("self.") # Total possible replacements
for key in md.keys():
original = "self." + md[key][dl_code] + "("
if original in line:
replacement = "self." + md[key][nl_code] + "("
new_line = line.replace(original, replacement)
found_swap = True
replace_count -= 1
if replace_count == 0:
break # Done making replacements
else:
# There might be another method to replace in the line.
# Example: self.assert_true("Name" in self.get_title())
line = new_line
continue
if found_swap:
if new_line.endswith(" # noqa"): # Remove flake8 skip
new_line = new_line[0:-len(" # noqa")]
new_data.append(new_line)
continue
new_data.append(line)
data = new_data
file = codecs.open(file_path, "w+", "utf-8")
file.writelines("\r\n".join(data))
file.close()
success = (
'\n' + c1 + '* Presentation: "' + file_name + '" was created! *'
'' + cr + '\n')
print(success)
if __name__ == "__main__":
invalid_run_command()

View File

@ -182,7 +182,7 @@ setup(
'Pillow==6.2.2;python_version<"3.5"',
'Pillow==7.2.0;python_version>="3.5" and python_version<"3.6"',
'Pillow==8.1.0;python_version>="3.6"',
'rich==9.9.0;python_version>="3.6" and python_version<"4.0"',
'rich==9.10.0;python_version>="3.6" and python_version<"4.0"',
'flake8==3.7.9;python_version<"3.5"',
'flake8==3.8.4;python_version>="3.5"',
'pyflakes==2.1.1;python_version<"3.5"',