Blacken'd

Leonardo Barcaroli 2019-01-21 17:59:44 +01:00
parent 2712c717d7
commit f02bfb1968
6 changed files with 174 additions and 178 deletions

View File

@ -3,5 +3,5 @@
"""Top-level package for Bot_Z.""" """Top-level package for Bot_Z."""
__author__ = """Leonardo Barcaroli""" __author__ = """Leonardo Barcaroli"""
__email__ = 'leonardo.barcaroli@deustechnology.com' __email__ = "leonardo.barcaroli@deustechnology.com"
__version__ = '0.1.0' __version__ = "0.1.0"

View File

@ -15,29 +15,32 @@ import time
import typing as T import typing as T
from urllib.parse import urlparse from urllib.parse import urlparse
geckoexe = shutil.which('geckodriver') geckoexe = shutil.which("geckodriver")
if geckoexe is None: if geckoexe is None:
local_path = pkg_resources.resource_filename(__name__, 'bin') local_path = pkg_resources.resource_filename(__name__, "bin")
try: try:
os.stat(os.path.join(local_path, 'geckodriver')) os.stat(os.path.join(local_path, "geckodriver"))
os.environ['PATH'] = os.environ['PATH'] + \ os.environ["PATH"] = os.environ["PATH"] + ":" + local_path
':' + local_path
except FileNotFoundError: except FileNotFoundError:
print("Missing geckodriver executable in path", file=sys.stderr) print("Missing geckodriver executable in path", file=sys.stderr)
raise raise
from selenium import webdriver as wd # noqa from selenium import webdriver as wd # noqa
from selenium.common.exceptions import WebDriverException, NoSuchElementException # noqa from selenium.common.exceptions import (
WebDriverException,
NoSuchElementException,
) # noqa
logging.basicConfig( logging.basicConfig(
level=os.environ.get('BOTZ_LOGLEVEL', logging.INFO), level=os.environ.get("BOTZ_LOGLEVEL", logging.INFO),
format='%(levelname)s: [%(name)s] -> %(message)s' format="%(levelname)s: [%(name)s] -> %(message)s",
) )
m_logger = logging.getLogger(__name__) m_logger = logging.getLogger(__name__)
m_logger.debug("Init at debug") m_logger.debug("Init at debug")
def safely(f: T.Callable) -> T.Callable: def safely(f: T.Callable) -> T.Callable:
def _protection(self, *args, **kwargs): def _protection(self, *args, **kwargs):
try: try:
@ -58,10 +61,9 @@ def _is_present(driver: wd.Firefox, xpath: str) -> bool:
return False return False
def is_present(driver: wd.Firefox, def is_present(
xpath: str, driver: wd.Firefox, xpath: str, timeout: T.Optional[timedelta] = None
timeout: T.Optional[timedelta]=None ) -> bool:
) -> bool:
""" """
Helper function. If an element is present in the DOM tree, Helper function. If an element is present in the DOM tree,
returns true. False otherwise. returns true. False otherwise.
@ -83,22 +85,24 @@ def is_present(driver: wd.Firefox,
class Operator(wd.Firefox): class Operator(wd.Firefox):
def __init__( def __init__(
self, self,
base_uri: str, base_uri: str,
name: str = None, name: str = None,
timeout: int=20, timeout: int = 20,
proxy: T.Optional[T.Tuple[str, int]] = None, proxy: T.Optional[T.Tuple[str, int]] = None,
headless: bool = True, headless: bool = True,
debug: bool = False, debug: bool = False,
*args, **kwargs) -> None: *args,
**kwargs
) -> None:
""" """
Adds some configuration to Firefox. Adds some configuration to Firefox.
""" """
self.profile = wd.FirefoxProfile() self.profile = wd.FirefoxProfile()
# Do not send telemetry # Do not send telemetry
self.profile.set_preference('datareporting.policy.dataSubmissionEnabled', False) self.profile.set_preference("datareporting.policy.dataSubmissionEnabled", False)
self.profile.set_preference('datareporting.healthreport.service.enabled', False) self.profile.set_preference("datareporting.healthreport.service.enabled", False)
self.profile.set_preference('datareporting.healthreport.uploadEnabled', False) self.profile.set_preference("datareporting.healthreport.uploadEnabled", False)
self.opts = wd.firefox.options.Options() self.opts = wd.firefox.options.Options()
self.opts.headless = headless self.opts.headless = headless
@ -109,13 +113,15 @@ class Operator(wd.Firefox):
self.timeout = timedelta(seconds=timeout) self.timeout = timedelta(seconds=timeout)
if proxy: if proxy:
self.profile.set_preference('network.proxy.type', 1) self.profile.set_preference("network.proxy.type", 1)
self.profile.set_preference('network.proxy.http', proxy[0]) self.profile.set_preference("network.proxy.http", proxy[0])
self.profile.set_preference('network.proxy.http_port', proxy[1]) self.profile.set_preference("network.proxy.http_port", proxy[1])
self.profile.set_preference('network.proxy.ssl', proxy[0]) self.profile.set_preference("network.proxy.ssl", proxy[0])
self.profile.set_preference('network.proxy.ssl_port', proxy[1]) self.profile.set_preference("network.proxy.ssl_port", proxy[1])
super().__init__(firefox_profile=self.profile, options=self.opts, *args, **kwargs) super().__init__(
firefox_profile=self.profile, options=self.opts, *args, **kwargs
)
self.fullscreen_window() self.fullscreen_window()
self.z_name = name if name is not None else __name__ self.z_name = name if name is not None else __name__
@ -127,7 +133,7 @@ class Operator(wd.Firefox):
self._checked_in = False self._checked_in = False
@safely @safely
def login(self, user: str, password: str, force: bool=False) -> None: def login(self, user: str, password: str, force: bool = False) -> None:
""" """
Do the login and proceed. Do the login and proceed.
""" """
@ -138,22 +144,22 @@ class Operator(wd.Firefox):
self.logger.info("Forcing login: %s", user) self.logger.info("Forcing login: %s", user)
# Retrieve login page # Retrieve login page
self.get(self.base_uri) self.get(self.base_uri)
_correct_url = 'cpccchk' in self.current_url _correct_url = "cpccchk" in self.current_url
_now = datetime.now() _now = datetime.now()
_elapsed = timedelta(seconds=0) _elapsed = timedelta(seconds=0)
while not _correct_url: while not _correct_url:
self.logger.debug("Not yet on login page: %s", self.current_url) self.logger.debug("Not yet on login page: %s", self.current_url)
time.sleep(0.5) time.sleep(0.5)
_correct_url = 'cpccchk' in self.current_url _correct_url = "cpccchk" in self.current_url
_elapsed = datetime.now() - _now _elapsed = datetime.now() - _now
if _elapsed > self.timeout: if _elapsed > self.timeout:
break break
self.logger.debug("After login get: %s", self.current_url) self.logger.debug("After login get: %s", self.current_url)
time.sleep(1) time.sleep(1)
# Username # Username
user_form = self.find_element_by_name('m_cUserName') user_form = self.find_element_by_name("m_cUserName")
# Password # Password
pass_form = self.find_element_by_name('m_cPassword') pass_form = self.find_element_by_name("m_cPassword")
# Login button # Login button
login_butt = self.find_element_by_xpath('//input[contains(@id, "_Accedi")]') login_butt = self.find_element_by_xpath('//input[contains(@id, "_Accedi")]')
# Compile and submit # Compile and submit
@ -167,7 +173,7 @@ class Operator(wd.Firefox):
login_butt.submit() login_butt.submit()
time.sleep(5) time.sleep(5)
self.logger.debug("Login result: %s", self.title) self.logger.debug("Login result: %s", self.title)
if 'Routine window' in self.title: if "Routine window" in self.title:
self.logger.debug("Reloading...") self.logger.debug("Reloading...")
self.refresh() self.refresh()
self.switch_to.alert.accept() self.switch_to.alert.accept()
@ -178,7 +184,7 @@ class Operator(wd.Firefox):
self.logger.error("Login failed: %s", user) self.logger.error("Login failed: %s", user)
@safely @safely
def logout(self, user: str, force: bool=False) -> None: def logout(self, user: str, force: bool = False) -> None:
""" """
Do the logout. Do the logout.
""" """
@ -188,7 +194,9 @@ class Operator(wd.Firefox):
return return
self.logger.info("Forcing logout: %s", user) self.logger.info("Forcing logout: %s", user)
# Find the Profile menu and open it # Find the Profile menu and open it
profile_butt = self.find_element_by_xpath('//span[contains(@id, "imgNoPhotoLabel")]') profile_butt = self.find_element_by_xpath(
'//span[contains(@id, "imgNoPhotoLabel")]'
)
profile_butt.click() profile_butt.click()
time.sleep(1) time.sleep(1)
# Find the logout button # Find the logout button
@ -205,14 +213,14 @@ class Operator(wd.Firefox):
Check if already logged in. Checks if page is '/jsp/home.jsp' Check if already logged in. Checks if page is '/jsp/home.jsp'
and if login cookie is set (and not expired). and if login cookie is set (and not expired).
""" """
_base_domain = '.'.join(self.uri.netloc.split('.')[-2:]) _base_domain = ".".join(self.uri.netloc.split(".")[-2:])
cookies = [c['name'] for c in self.get_cookies() if _base_domain in c['domain']] cookies = [c["name"] for c in self.get_cookies() if _base_domain in c["domain"]]
_right_url = "/jsp/home.jsp" in self.current_url _right_url = "/jsp/home.jsp" in self.current_url
_cookies = "dtLatC" in cookies _cookies = "dtLatC" in cookies
return _right_url and _cookies return _right_url and _cookies
@safely @safely
def check_in(self, force: bool=False) -> None: def check_in(self, force: bool = False) -> None:
""" """
Click the check in button. Click the check in button.
""" """
@ -223,7 +231,9 @@ class Operator(wd.Firefox):
self.logger.warn("Already checked in!") self.logger.warn("Already checked in!")
if not force: if not force:
return return
iframe = self.find_element_by_xpath('//iframe[contains(@id, "gsmd_container.jsp")]') iframe = self.find_element_by_xpath(
'//iframe[contains(@id, "gsmd_container.jsp")]'
)
self.switch_to.frame(iframe) self.switch_to.frame(iframe)
enter_butt = self.find_element_by_xpath('//input[@value="Entrata"]') enter_butt = self.find_element_by_xpath('//input[@value="Entrata"]')
enter_butt.click() enter_butt.click()
@ -232,7 +242,7 @@ class Operator(wd.Firefox):
pass pass
@safely @safely
def check_out(self, force: bool=False) -> None: def check_out(self, force: bool = False) -> None:
""" """
Click the check out button. Click the check out button.
""" """
@ -243,7 +253,9 @@ class Operator(wd.Firefox):
self.logger.warn("Not yet checked in!") self.logger.warn("Not yet checked in!")
if not force: if not force:
return return
iframe = self.find_element_by_xpath('//iframe[contains(@id, "gsmd_container.jsp")]') iframe = self.find_element_by_xpath(
'//iframe[contains(@id, "gsmd_container.jsp")]'
)
self.switch_to.frame(iframe) self.switch_to.frame(iframe)
exit_butt = self.find_element_by_xpath('//input[@value="Uscita"]') exit_butt = self.find_element_by_xpath('//input[@value="Uscita"]')
exit_butt.click() exit_butt.click()

View File

@ -8,8 +8,7 @@ import click
@click.command() @click.command()
def main(args=None): def main(args=None):
"""Console script for bot_z.""" """Console script for bot_z."""
click.echo("Replace this message by putting your code into " click.echo("Replace this message by putting your code into " "bot_z.cli.main")
"bot_z.cli.main")
click.echo("See click documentation at http://click.pocoo.org/") click.echo("See click documentation at http://click.pocoo.org/")

View File

@ -20,7 +20,7 @@ import os
# directory, add these directories to sys.path here. If the directory is # directory, add these directories to sys.path here. If the directory is
# relative to the documentation root, use os.path.abspath to make it # relative to the documentation root, use os.path.abspath to make it
# absolute, like shown here. # absolute, like shown here.
#sys.path.insert(0, os.path.abspath('.')) # sys.path.insert(0, os.path.abspath('.'))
# Get the project root dir, which is the parent dir of this # Get the project root dir, which is the parent dir of this
cwd = os.getcwd() cwd = os.getcwd()
@ -36,26 +36,26 @@ import bot_z
# -- General configuration --------------------------------------------- # -- General configuration ---------------------------------------------
# If your documentation needs a minimal Sphinx version, state it here. # If your documentation needs a minimal Sphinx version, state it here.
#needs_sphinx = '1.0' # needs_sphinx = '1.0'
# Add any Sphinx extension module names here, as strings. They can be # Add any Sphinx extension module names here, as strings. They can be
# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom ones. # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom ones.
extensions = ['sphinx.ext.autodoc', 'sphinx.ext.viewcode'] extensions = ["sphinx.ext.autodoc", "sphinx.ext.viewcode"]
# Add any paths that contain templates here, relative to this directory. # Add any paths that contain templates here, relative to this directory.
templates_path = ['_templates'] templates_path = ["_templates"]
# The suffix of source filenames. # The suffix of source filenames.
source_suffix = '.rst' source_suffix = ".rst"
# The encoding of source files. # The encoding of source files.
#source_encoding = 'utf-8-sig' # source_encoding = 'utf-8-sig'
# The master toctree document. # The master toctree document.
master_doc = 'index' master_doc = "index"
# General information about the project. # General information about the project.
project = u'Bot_Z' project = u"Bot_Z"
copyright = u"2019, Leonardo Barcaroli" copyright = u"2019, Leonardo Barcaroli"
# The version info for the project you're documenting, acts as replacement # The version info for the project you're documenting, acts as replacement
@ -69,126 +69,126 @@ release = bot_z.__version__
# The language for content autogenerated by Sphinx. Refer to documentation # The language for content autogenerated by Sphinx. Refer to documentation
# for a list of supported languages. # for a list of supported languages.
#language = None # language = None
# There are two options for replacing |today|: either, you set today to # There are two options for replacing |today|: either, you set today to
# some non-false value, then it is used: # some non-false value, then it is used:
#today = '' # today = ''
# Else, today_fmt is used as the format for a strftime call. # Else, today_fmt is used as the format for a strftime call.
#today_fmt = '%B %d, %Y' # today_fmt = '%B %d, %Y'
# List of patterns, relative to source directory, that match files and # List of patterns, relative to source directory, that match files and
# directories to ignore when looking for source files. # directories to ignore when looking for source files.
exclude_patterns = ['_build'] exclude_patterns = ["_build"]
# The reST default role (used for this markup: `text`) to use for all # The reST default role (used for this markup: `text`) to use for all
# documents. # documents.
#default_role = None # default_role = None
# If true, '()' will be appended to :func: etc. cross-reference text. # If true, '()' will be appended to :func: etc. cross-reference text.
#add_function_parentheses = True # add_function_parentheses = True
# If true, the current module name will be prepended to all description # If true, the current module name will be prepended to all description
# unit titles (such as .. function::). # unit titles (such as .. function::).
#add_module_names = True # add_module_names = True
# If true, sectionauthor and moduleauthor directives will be shown in the # If true, sectionauthor and moduleauthor directives will be shown in the
# output. They are ignored by default. # output. They are ignored by default.
#show_authors = False # show_authors = False
# The name of the Pygments (syntax highlighting) style to use. # The name of the Pygments (syntax highlighting) style to use.
pygments_style = 'sphinx' pygments_style = "sphinx"
# A list of ignored prefixes for module index sorting. # A list of ignored prefixes for module index sorting.
#modindex_common_prefix = [] # modindex_common_prefix = []
# If true, keep warnings as "system message" paragraphs in the built # If true, keep warnings as "system message" paragraphs in the built
# documents. # documents.
#keep_warnings = False # keep_warnings = False
# -- Options for HTML output ------------------------------------------- # -- Options for HTML output -------------------------------------------
# The theme to use for HTML and HTML Help pages. See the documentation for # The theme to use for HTML and HTML Help pages. See the documentation for
# a list of builtin themes. # a list of builtin themes.
html_theme = 'default' html_theme = "default"
# Theme options are theme-specific and customize the look and feel of a # Theme options are theme-specific and customize the look and feel of a
# theme further. For a list of options available for each theme, see the # theme further. For a list of options available for each theme, see the
# documentation. # documentation.
#html_theme_options = {} # html_theme_options = {}
# Add any paths that contain custom themes here, relative to this directory. # Add any paths that contain custom themes here, relative to this directory.
#html_theme_path = [] # html_theme_path = []
# The name for this set of Sphinx documents. If None, it defaults to # The name for this set of Sphinx documents. If None, it defaults to
# "<project> v<release> documentation". # "<project> v<release> documentation".
#html_title = None # html_title = None
# A shorter title for the navigation bar. Default is the same as # A shorter title for the navigation bar. Default is the same as
# html_title. # html_title.
#html_short_title = None # html_short_title = None
# The name of an image file (relative to this directory) to place at the # The name of an image file (relative to this directory) to place at the
# top of the sidebar. # top of the sidebar.
#html_logo = None # html_logo = None
# The name of an image file (within the static path) to use as favicon # The name of an image file (within the static path) to use as favicon
# of the docs. This file should be a Windows icon file (.ico) being # of the docs. This file should be a Windows icon file (.ico) being
# 16x16 or 32x32 pixels large. # 16x16 or 32x32 pixels large.
#html_favicon = None # html_favicon = None
# Add any paths that contain custom static files (such as style sheets) # Add any paths that contain custom static files (such as style sheets)
# here, relative to this directory. They are copied after the builtin # here, relative to this directory. They are copied after the builtin
# static files, so a file named "default.css" will overwrite the builtin # static files, so a file named "default.css" will overwrite the builtin
# "default.css". # "default.css".
html_static_path = ['_static'] html_static_path = ["_static"]
# If not '', a 'Last updated on:' timestamp is inserted at every page # If not '', a 'Last updated on:' timestamp is inserted at every page
# bottom, using the given strftime format. # bottom, using the given strftime format.
#html_last_updated_fmt = '%b %d, %Y' # html_last_updated_fmt = '%b %d, %Y'
# If true, SmartyPants will be used to convert quotes and dashes to # If true, SmartyPants will be used to convert quotes and dashes to
# typographically correct entities. # typographically correct entities.
#html_use_smartypants = True # html_use_smartypants = True
# Custom sidebar templates, maps document names to template names. # Custom sidebar templates, maps document names to template names.
#html_sidebars = {} # html_sidebars = {}
# Additional templates that should be rendered to pages, maps page names # Additional templates that should be rendered to pages, maps page names
# to template names. # to template names.
#html_additional_pages = {} # html_additional_pages = {}
# If false, no module index is generated. # If false, no module index is generated.
#html_domain_indices = True # html_domain_indices = True
# If false, no index is generated. # If false, no index is generated.
#html_use_index = True # html_use_index = True
# If true, the index is split into individual pages for each letter. # If true, the index is split into individual pages for each letter.
#html_split_index = False # html_split_index = False
# If true, links to the reST sources are added to the pages. # If true, links to the reST sources are added to the pages.
#html_show_sourcelink = True # html_show_sourcelink = True
# If true, "Created using Sphinx" is shown in the HTML footer. # If true, "Created using Sphinx" is shown in the HTML footer.
# Default is True. # Default is True.
#html_show_sphinx = True # html_show_sphinx = True
# If true, "(C) Copyright ..." is shown in the HTML footer. # If true, "(C) Copyright ..." is shown in the HTML footer.
# Default is True. # Default is True.
#html_show_copyright = True # html_show_copyright = True
# If true, an OpenSearch description file will be output, and all pages # If true, an OpenSearch description file will be output, and all pages
# will contain a <link> tag referring to it. The value of this option # will contain a <link> tag referring to it. The value of this option
# must be the base URL from which the finished HTML is served. # must be the base URL from which the finished HTML is served.
#html_use_opensearch = '' # html_use_opensearch = ''
# This is the file name suffix for HTML files (e.g. ".xhtml"). # This is the file name suffix for HTML files (e.g. ".xhtml").
#html_file_suffix = None # html_file_suffix = None
# Output file base name for HTML help builder. # Output file base name for HTML help builder.
htmlhelp_basename = 'bot_zdoc' htmlhelp_basename = "bot_zdoc"
# -- Options for LaTeX output ------------------------------------------ # -- Options for LaTeX output ------------------------------------------
@ -196,10 +196,8 @@ htmlhelp_basename = 'bot_zdoc'
latex_elements = { latex_elements = {
# The paper size ('letterpaper' or 'a4paper'). # The paper size ('letterpaper' or 'a4paper').
#'papersize': 'letterpaper', #'papersize': 'letterpaper',
# The font size ('10pt', '11pt' or '12pt'). # The font size ('10pt', '11pt' or '12pt').
#'pointsize': '10pt', #'pointsize': '10pt',
# Additional stuff for the LaTeX preamble. # Additional stuff for the LaTeX preamble.
#'preamble': '', #'preamble': '',
} }
@ -208,44 +206,38 @@ latex_elements = {
# (source start file, target name, title, author, documentclass # (source start file, target name, title, author, documentclass
# [howto/manual]). # [howto/manual]).
latex_documents = [ latex_documents = [
('index', 'bot_z.tex', ("index", "bot_z.tex", u"Bot_Z Documentation", u"Leonardo Barcaroli", "manual")
u'Bot_Z Documentation',
u'Leonardo Barcaroli', 'manual'),
] ]
# The name of an image file (relative to this directory) to place at # The name of an image file (relative to this directory) to place at
# the top of the title page. # the top of the title page.
#latex_logo = None # latex_logo = None
# For "manual" documents, if this is true, then toplevel headings # For "manual" documents, if this is true, then toplevel headings
# are parts, not chapters. # are parts, not chapters.
#latex_use_parts = False # latex_use_parts = False
# If true, show page references after internal links. # If true, show page references after internal links.
#latex_show_pagerefs = False # latex_show_pagerefs = False
# If true, show URL addresses after external links. # If true, show URL addresses after external links.
#latex_show_urls = False # latex_show_urls = False
# Documents to append as an appendix to all manuals. # Documents to append as an appendix to all manuals.
#latex_appendices = [] # latex_appendices = []
# If false, no module index is generated. # If false, no module index is generated.
#latex_domain_indices = True # latex_domain_indices = True
# -- Options for manual page output ------------------------------------ # -- Options for manual page output ------------------------------------
# One entry per manual page. List of tuples # One entry per manual page. List of tuples
# (source start file, name, description, authors, manual section). # (source start file, name, description, authors, manual section).
man_pages = [ man_pages = [("index", "bot_z", u"Bot_Z Documentation", [u"Leonardo Barcaroli"], 1)]
('index', 'bot_z',
u'Bot_Z Documentation',
[u'Leonardo Barcaroli'], 1)
]
# If true, show URL addresses after external links. # If true, show URL addresses after external links.
#man_show_urls = False # man_show_urls = False
# -- Options for Texinfo output ---------------------------------------- # -- Options for Texinfo output ----------------------------------------
@ -254,22 +246,25 @@ man_pages = [
# (source start file, target name, title, author, # (source start file, target name, title, author,
# dir menu entry, description, category) # dir menu entry, description, category)
texinfo_documents = [ texinfo_documents = [
('index', 'bot_z', (
u'Bot_Z Documentation', "index",
u'Leonardo Barcaroli', "bot_z",
'bot_z', u"Bot_Z Documentation",
'One line description of project.', u"Leonardo Barcaroli",
'Miscellaneous'), "bot_z",
"One line description of project.",
"Miscellaneous",
)
] ]
# Documents to append as an appendix to all manuals. # Documents to append as an appendix to all manuals.
#texinfo_appendices = [] # texinfo_appendices = []
# If false, no module index is generated. # If false, no module index is generated.
#texinfo_domain_indices = True # texinfo_domain_indices = True
# How to display URL addresses: 'footnote', 'no', or 'inline'. # How to display URL addresses: 'footnote', 'no', or 'inline'.
#texinfo_show_urls = 'footnote' # texinfo_show_urls = 'footnote'
# If true, do not generate a @detailmenu in the "Top" node's menu. # If true, do not generate a @detailmenu in the "Top" node's menu.
#texinfo_no_detailmenu = False # texinfo_no_detailmenu = False

112
setup.py
View File

@ -21,25 +21,20 @@ import zipfile
GECKO_RELEASE_PATH = "https://github.com/mozilla/geckodriver" GECKO_RELEASE_PATH = "https://github.com/mozilla/geckodriver"
PKG_NAME = 'bot_z' PKG_NAME = "bot_z"
VERSION = '0.1.0' VERSION = "0.1.0"
AUTHOR = 'blallo' AUTHOR = "blallo"
AUTHOR_EMAIL = 'blallo@autistici.org' AUTHOR_EMAIL = "blallo@autistici.org"
BIN_PATH = 'bin/geckodriver' BIN_PATH = "bin/geckodriver"
with open('README.md') as readme_file: with open("README.md") as readme_file:
readme = readme_file.read() readme = readme_file.read()
requirements = [ requirements = ["Click>=6.0", "selenium>=3.141.0"]
'Click>=6.0',
'selenium>=3.141.0',
]
setup_requirements = [ setup_requirements = [] # type: T.List[str]
] # type: T.List[str]
test_requirements = [ test_requirements = [] # type: T.List[str]
] # type: T.List[str]
class GitTags(HTMLParser): class GitTags(HTMLParser):
@ -48,7 +43,7 @@ class GitTags(HTMLParser):
def handle_starttag(self, tag, attrs): def handle_starttag(self, tag, attrs):
dattrs = dict(attrs) dattrs = dict(attrs)
if 'commit-title' in dattrs.get('class', ''): if "commit-title" in dattrs.get("class", ""):
self.take_next = 1 self.take_next = 1
def handle_data(self, data): def handle_data(self, data):
@ -57,7 +52,7 @@ class GitTags(HTMLParser):
elif self.take_next == 1: elif self.take_next == 1:
self.take_next = 2 self.take_next = 2
elif self.take_next == 2: elif self.take_next == 2:
self.tags.append(data.strip('\n').strip(' ').strip('\n')) self.tags.append(data.strip("\n").strip(" ").strip("\n"))
self.take_next = 0 self.take_next = 0
@ -83,9 +78,9 @@ def find_latest_version(url: str) -> str:
""" """
Retrieves latest geckodriver tag. Retrieves latest geckodriver tag.
""" """
tag_page = retrieve_page('{}/tags'.format(url)) tag_page = retrieve_page("{}/tags".format(url))
gt = GitTags() gt = GitTags()
gt.feed(tag_page.decode('utf-8')) gt.feed(tag_page.decode("utf-8"))
gt.tags.sort() gt.tags.sort()
return gt.tags[-1] return gt.tags[-1]
@ -109,9 +104,8 @@ def ensure_local_folder() -> None:
def assemble_driver_uri( def assemble_driver_uri(
version: T.Optional[str]=None, version: T.Optional[str] = None, platform: T.Optional[str] = None
platform: T.Optional[str]=None ) -> str:
) -> str:
""" """
Selects the right geckodriver URI. Selects the right geckodriver URI.
""" """
@ -120,20 +114,17 @@ def assemble_driver_uri(
version = find_latest_version(GECKO_RELEASE_PATH) version = find_latest_version(GECKO_RELEASE_PATH)
if not platform: if not platform:
s_platform = sys.platform s_platform = sys.platform
is_64bits = sys.maxsize > 2**32 is_64bits = sys.maxsize > 2 ** 32
if is_64bits: if is_64bits:
platform = '{}64'.format(s_platform) platform = "{}64".format(s_platform)
else: else:
platform = '{}64'.format(s_platform) platform = "{}64".format(s_platform)
if 'win' in platform: if "win" in platform:
ext = 'zip' ext = "zip"
else: else:
ext = 'tar.gz' ext = "tar.gz"
return '{base}/releases/download/{vers}/geckodriver-{vers}-{platform}.{ext}'.format( return "{base}/releases/download/{vers}/geckodriver-{vers}-{platform}.{ext}".format(
base=GECKO_RELEASE_PATH, base=GECKO_RELEASE_PATH, vers=version, platform=platform, ext=ext
vers=version,
platform=platform,
ext=ext
) )
@ -141,18 +132,18 @@ def download_driver_bin(uri: str, path: str) -> None:
""" """
Donwloads the geckodriver binary. Donwloads the geckodriver binary.
""" """
name = uri.split('/')[-1] name = uri.split("/")[-1]
filepath = os.path.join(path, name) filepath = os.path.join(path, name)
print("[DRIVER] downloading '{}' to {}".format(uri, filepath)) print("[DRIVER] downloading '{}' to {}".format(uri, filepath))
content = retrieve_page(uri) content = retrieve_page(uri)
try: try:
with open(filepath, 'wb') as f: with open(filepath, "wb") as f:
f.write(content) f.write(content)
if name.endswith(".zip"): if name.endswith(".zip"):
with zipfile.ZipFile(filepath, 'r') as z: with zipfile.ZipFile(filepath, "r") as z:
z.extractall(path) z.extractall(path)
elif name.endswith(".tar.gz"): elif name.endswith(".tar.gz"):
with tarfile.open(filepath, 'r') as r: with tarfile.open(filepath, "r") as r:
r.extractall(path) r.extractall(path)
else: else:
raise RuntimeError("Unrecognized file extension: %s", name) raise RuntimeError("Unrecognized file extension: %s", name)
@ -160,15 +151,15 @@ def download_driver_bin(uri: str, path: str) -> None:
os.remove(filepath) os.remove(filepath)
def preinstall(platform: T.Optional[str]=None) -> None: def preinstall(platform: T.Optional[str] = None) -> None:
""" """
Performs all the postintallation flow, donwloading in the Performs all the postintallation flow, donwloading in the
right place the geckodriver binary. right place the geckodriver binary.
""" """
# target_path = os.path.join(os.path.abspath(os.path.curdir), 'bot_z', 'bin') # target_path = os.path.join(os.path.abspath(os.path.curdir), 'bot_z', 'bin')
target_path = pkg_resources.resource_filename('bot_z', 'bin') target_path = pkg_resources.resource_filename("bot_z", "bin")
ensure_local_folder() ensure_local_folder()
version = os.environ.get('BOTZ_GECKO_VERSION') version = os.environ.get("BOTZ_GECKO_VERSION")
gecko_uri = assemble_driver_uri(version, platform) gecko_uri = assemble_driver_uri(version, platform)
print("[POSTINSTALL] gecko_uri: {}".format(gecko_uri)) print("[POSTINSTALL] gecko_uri: {}".format(gecko_uri))
download_driver_bin(gecko_uri, target_path) download_driver_bin(gecko_uri, target_path)
@ -199,6 +190,7 @@ def translate_platform_to_gecko_vers(plat: str) -> str:
# From: https://stackoverflow.com/a/36902139 # From: https://stackoverflow.com/a/36902139
class CustomDevelopCommand(develop): class CustomDevelopCommand(develop):
"""Custom installation for development mode.""" """Custom installation for development mode."""
def run(self): def run(self):
print("POSTINSTALL") print("POSTINSTALL")
preinstall() preinstall()
@ -207,12 +199,13 @@ class CustomDevelopCommand(develop):
class CustomInstallCommand(install): class CustomInstallCommand(install):
"""Custom installation for installation mode.""" """Custom installation for installation mode."""
def run(self): def run(self):
opts = self.distribution.get_cmdline_options() opts = self.distribution.get_cmdline_options()
platform = None platform = None
if 'bdist_wheel' in opts: if "bdist_wheel" in opts:
platform = translate_platform_to_gecko_vers( platform = translate_platform_to_gecko_vers(
opts['bdist_wheel'].get('plat-name') opts["bdist_wheel"].get("plat-name")
) )
preinstall(platform) preinstall(platform)
super().run() super().run()
@ -224,13 +217,14 @@ class CustomBDistWheel(bdist_wheel):
Custom bdist_wheel command to ship the right binary Custom bdist_wheel command to ship the right binary
of geckodriver. of geckodriver.
""" """
def finalize_options(self): def finalize_options(self):
super().finalize_options() super().finalize_options()
self.root_is_pure = False self.root_is_pure = False
def get_tag(self): def get_tag(self):
python, abi, plat = super().get_tag() python, abi, plat = super().get_tag()
python, abi = 'py3', 'none' python, abi = "py3", "none"
return python, abi, plat return python, abi, plat
@ -241,33 +235,29 @@ setup(
long_description=readme, long_description=readme,
author=AUTHOR, author=AUTHOR,
author_email=AUTHOR_EMAIL, author_email=AUTHOR_EMAIL,
url='https://git.abbiamoundominio.org/blallo/BotZ', url="https://git.abbiamoundominio.org/blallo/BotZ",
packages=find_packages(include=['bot_z']), packages=find_packages(include=["bot_z"]),
cmdclass={ cmdclass={
'develop': CustomDevelopCommand, "develop": CustomDevelopCommand,
'install': CustomInstallCommand, "install": CustomInstallCommand,
'bdist_wheel': CustomBDistWheel, "bdist_wheel": CustomBDistWheel,
}, },
entry_points={ entry_points={"console_scripts": ["bot_z=bot_z.cli:main"]},
'console_scripts': [ package_data={"bot_z": ["bin/geckodriver"]},
'bot_z=bot_z.cli:main'
]
},
package_data = {'bot_z': ['bin/geckodriver']},
include_package_data=True, include_package_data=True,
install_requires=requirements, install_requires=requirements,
license="GLWTS Public Licence", license="GLWTS Public Licence",
zip_safe=False, zip_safe=False,
keywords='bot_z', keywords="bot_z",
classifiers=[ classifiers=[
'Development Status :: 2 - Pre-Alpha', "Development Status :: 2 - Pre-Alpha",
'Intended Audience :: Developers', "Intended Audience :: Developers",
'License :: GLWTS Public Licence', "License :: GLWTS Public Licence",
'Natural Language :: English', "Natural Language :: English",
'Programming Language :: Python :: 3', "Programming Language :: Python :: 3",
'Programming Language :: Python :: 3.7', "Programming Language :: Python :: 3.7",
], ],
test_suite='pytest', test_suite="pytest",
tests_require=test_requirements, tests_require=test_requirements,
setup_requires=setup_requirements, setup_requires=setup_requirements,
) )

View File

@ -28,7 +28,7 @@ class TestBot_z(unittest.TestCase):
runner = CliRunner() runner = CliRunner()
result = runner.invoke(cli.main) result = runner.invoke(cli.main)
assert result.exit_code == 0 assert result.exit_code == 0
assert 'bot_z.cli.main' in result.output assert "bot_z.cli.main" in result.output
help_result = runner.invoke(cli.main, ['--help']) help_result = runner.invoke(cli.main, ["--help"])
assert help_result.exit_code == 0 assert help_result.exit_code == 0
assert '--help Show this message and exit.' in help_result.output assert "--help Show this message and exit." in help_result.output