From e46c84670498b996ee009d74255e5d86e80f42b4 Mon Sep 17 00:00:00 2001 From: Vishal Kumar Mishra Date: Sat, 2 Sep 2023 02:48:02 +0530 Subject: [PATCH] Port to Python 3.10, bug fixes and code quality imporvements --- .gitignore | 3 + .pre-commit-config.yaml | 69 +- .sourcery.yaml | 41 ++ README.md | 99 +-- bandit.yaml | 10 + example.env | 0 log_config.yaml | 30 + poetry.lock | 691 ++++++++++--------- pyproject.toml | 98 ++- read_envs.py | 9 + requirements.txt | 25 - requirements/base.txt | 5 + requirements/dev.txt | 10 + settings.py | 48 ++ {scripts => simpleselenium}/sample_script.py | 0 simpleselenium/scripts.py | 7 + simpleselenium/selenium_requests.py | 301 ++++---- tests/test_basic_function.py | 4 +- tox.ini | 2 +- utility_scripts/bandit_util.sh | 5 + utility_scripts/black_util.sh | 4 + utility_scripts/codelimit_util.sh | 3 + utility_scripts/mypy_util.sh | 6 + utility_scripts/pre_commit_util.sh | 7 + utility_scripts/ruff_util.sh | 7 + utility_scripts/sourcery_utils.sh | 8 + 26 files changed, 948 insertions(+), 544 deletions(-) mode change 100755 => 100644 .pre-commit-config.yaml create mode 100644 .sourcery.yaml create mode 100644 bandit.yaml delete mode 100755 example.env create mode 100644 log_config.yaml create mode 100644 read_envs.py delete mode 100755 requirements.txt create mode 100644 requirements/base.txt create mode 100644 requirements/dev.txt create mode 100644 settings.py rename {scripts => simpleselenium}/sample_script.py (100%) create mode 100644 simpleselenium/scripts.py create mode 100644 utility_scripts/bandit_util.sh create mode 100644 utility_scripts/black_util.sh create mode 100644 utility_scripts/codelimit_util.sh create mode 100644 utility_scripts/mypy_util.sh create mode 100644 utility_scripts/pre_commit_util.sh create mode 100644 utility_scripts/ruff_util.sh create mode 100644 utility_scripts/sourcery_utils.sh diff --git a/.gitignore b/.gitignore index 0579d17..7d4f4ab 100755 --- a/.gitignore +++ b/.gitignore @@ -132,3 +132,6 @@ dmypy.json # Pyre type checker .pyre/ + +# Cache +**_cache** diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml old mode 100755 new mode 100644 index 2b2403a..21a6777 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,37 +1,66 @@ -exclude: '.git|.tox' +exclude: '^docs/|/migrations/' default_stages: [ commit ] -fail_fast: true repos: - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v3.4.0 + rev: v4.4.0 hooks: - id: trailing-whitespace - id: end-of-file-fixer + - id: check-json - id: check-toml - - id: check-merge-conflict + - id: check-xml + - id: check-yaml + - id: debug-statements + - id: check-builtin-literals + - id: check-case-conflict + - id: check-docstring-first + - id: detect-private-key - - repo: https://github.com/psf/black - rev: 20.8b1 + - repo: https://github.com/asottile/pyupgrade + rev: v3.7.0 hooks: - - id: black + - id: pyupgrade + args: [ --py311-plus ] - - repo: https://github.com/timothycrosley/isort - rev: 5.6.4 + - repo: https://github.com/PyCQA/isort + rev: 5.12.0 hooks: - id: isort - - repo: https://gitlab.com/pycqa/flake8 - rev: 3.8.4 + - repo: https://github.com/astral-sh/ruff-pre-commit + # Ruff: https://github.com/astral-sh/ruff-pre-commit + rev: v0.0.280 + hooks: + - id: ruff + args: [ --fix, --exit-non-zero-on-fix ] + types_or: [ python, pyi, jupyter ] + + - repo: https://github.com/psf/black + rev: 23.3.0 + hooks: + - id: black + + - repo: https://github.com/PyCQA/bandit + rev: 1.7.5 hooks: - - id: flake8 - additional_dependencies: [ flake8-isort ] + - id: bandit + args: [ "-c", "bandit.yaml" ] - - repo: local + - repo: https://github.com/sourcery-ai/sourcery + # Source: https://docs.sourcery.ai/Guides/Getting-Started/Pre-Commit/ + # https://sourcery.ai/blog/python-best-practices/ + rev: v1.6.0 hooks: - - id: pytest-check - name: pytest-check - entry: pytest - language: system - pass_filenames: false - always_run: true + - id: sourcery + # only check the files which have changed: does not work on first commit + args: [ --diff=git diff HEAD, --no-summary ] + # args: [ --diff=git diff HEAD, --fix, --no-summary ] + # args: [ --fix, --no-summary ] + + +# sets up .pre-commit-ci.yaml to ensure pre-commit dependencies stay up to date +ci: + autoupdate_schedule: weekly + skip: [ ] + submodules: false diff --git a/.sourcery.yaml b/.sourcery.yaml new file mode 100644 index 0000000..3702439 --- /dev/null +++ b/.sourcery.yaml @@ -0,0 +1,41 @@ +#https://docs.sourcery.ai/Reference/Configuration/sourcery-yaml/ + +ignore: + - .git + - venv + - .venv + - env + - .env + - .tox + - node_modules + - vendor + - repository_directory + +rule_settings: + enable: [default] + disable: [] + rule_types: + - refactoring + - suggestion + - comment + python_version: '3.11' + +rules: [] + +metrics: + quality_threshold: 25.0 + +github: + labels: [] + ignore_labels: + - sourcery-ignore + request_review: author + sourcery_branch: sourcery/{base_branch} + +clone_detection: + min_lines: 3 + min_duplicates: 2 + identical_clones_only: false + +proxy: + no_ssl_verify: false diff --git a/README.md b/README.md index 32059b2..592618b 100755 --- a/README.md +++ b/README.md @@ -1,6 +1,8 @@ ### Simple Selenium -The aim of this package is to quickly get started with working with selenium for simple browser automation tasks. +Selenium with Tab Management + + With all the already available flexibility and features of Selenium ### Installation @@ -10,13 +12,36 @@ Install from PyPI pip install simpleselenium ``` +### Core Idea + +A `browser` has many `tabs`. + +Action/activity on `Tab` object +- Check if the tab is alive (i.e. it has not been closed) +- Switch to a tab +- See/obtain page source, title and headings +- work on a specific tab (click elements, scroll and so on.) +- ... many more + +Action/activity on `Browser` object +- Open a new tab with url +- Get a list of open tabs +- Get active tab +- Switch to a tab of the browser (first, last or any one). +- close a tab +- Close the browser + +### Working with driver objects + +`driver` object available on any `Tab` object. + ### Features -Some basic feature is being listed below. +Some basic features are being listed below (also see the `Usage` section below): - easy management of different tabs - switching to a tab is super easy -- know if a tab is active or alive +- know if a tab is active (current tab) or alive - closing a tab is easy as `browser.close_tab(tab_object)` - Several (built-in) functions - `tab.infinite_scroll()` @@ -35,58 +60,54 @@ method off it which returns a Tab object. #### Browser ```python -import time # just to slow down stuffs and see things for testing -from simpleselenium import Browser -chrome_driver = r"/path/to/chromedriver" +from simpleselenium import Browser, Tab -with Browser(name="Chrome", driver_path=chrome_driver, implicit_wait=10) as browser: - google = browser.open("https://google.com") - yahoo = browser.open("https://yahoo.com") - bing = browser.open("https://bing.com") - duck_duck = browser.open("https://duckduckgo.com/") +# name is one of "Chrome" or "FireFox" +# driver path is not required in most cases - print(yahoo) # A Tab Object - print(yahoo.is_alive) - print(yahoo.is_active) - print(dir(yahoo)) # All methods and attributes of Tab Objects +with Browser(name="Chrome", driver_path=None, implicit_wait=10) as browser: + google: Tab = browser.open("https://google.com") # a `Tab` object + yahoo = browser.open("https://yahoo.com") + bing = browser.open("https://bing.com") + duck_duck = browser.open("https://duckduckgo.com/") - print(browser.get_all_tabs()) # List of tab objects + yahoo.scroll_up(times=5) + yahoo.scroll_down(times=10) - print(browser.tabs.all()) - print(browser.tabs) # TabManager object - print(dir(browser.tabs)) # All methods and attributes of TabManager Objects + print(browser.tabs) + print(browser.current_tab) + print(browser.first_tab) + print(browser.last_tab) - browser.close_tab(bing) # close a browser tab - print(browser.tabs.all()) + print(browser.last_tab.switch()) - print(browser.get_current_tab()) # current tab - time.sleep(5) + print(google.page_source) + print(google.title) + print(google.url) + print(google.is_active) + print(google.is_alive) - yahoo.switch() # switch/focus/tap to/on `yahoo` tab - print(browser.get_current_tab()) - time.sleep(5) + browser.close_tab(bing) + print(browser.tabs) - google.switch() - print(browser.get_current_tab()) - time.sleep(5) + print(browser.current_tab) - browser.close_tab(yahoo) - time.sleep(5) + yahoo.switch() + print(browser.current_tab) + google.switch() - print(google.driver) # Usual selenium driver object which can be worked upon + print(browser.current_tab) - print(google.driver.title, google.title) + browser.close_tab(yahoo) - print(google.scroll_to_bottom()) - print(google.is_active) - print(google.is_alive) - print(bing.is_alive) # False, it has been deleted. + print(yahoo.is_alive) + print(yahoo.is_active) - print(browser.get_all_tabs()) + print(google.driver.title, google.title) + print(google.driver.title == google.title) ``` ### TODO - Complete documentation -- Test Code diff --git a/bandit.yaml b/bandit.yaml new file mode 100644 index 0000000..2ad0c88 --- /dev/null +++ b/bandit.yaml @@ -0,0 +1,10 @@ +# also see pyproject.toml for [tool.bandit] +# https://bandit.readthedocs.io/en/1.7.5/config.html +# exclude = tests,path/to/file +# tests = B201,B301 + +exclude_dirs: [ 'tests/', 'repository_directory/', "tox" ] +tests: [ ] +skips: [ + 'B113' # request.get without a timeout +] diff --git a/example.env b/example.env deleted file mode 100755 index e69de29..0000000 diff --git a/log_config.yaml b/log_config.yaml new file mode 100644 index 0000000..f81a3f3 --- /dev/null +++ b/log_config.yaml @@ -0,0 +1,30 @@ +# YAML copied from: https://realpython.com/python-logging/#other-configuration-methods +version: 1 +formatters: + extended: + format: '%(asctime)-20s :: %(levelname)-8s :: [%(process)d]%(processName)s :: %(threadName)s[%(thread)d] :: %(pathname)s :: %(lineno)d :: %(message)s' + simple: + format: '%(asctime)s - %(name)s - %(levelname)s - %(message)s' +handlers: + console: + class: logging.StreamHandler + level: DEBUG + formatter: simple + stream: ext://sys.stdout + file_handler: + class: logging.handlers.RotatingFileHandler + level: DEBUG + formatter: extended + filename: info.log + encoding: utf8 + maxBytes: 10000 + backupCount: 10 + mode: a +loggers: + sampleLogger: + level: DEBUG + handlers: [console, file_handler] + propagate: no +root: + level: DEBUG + handlers: [console, file_handler] diff --git a/poetry.lock b/poetry.lock index d574e6d..94ac8ae 100644 --- a/poetry.lock +++ b/poetry.lock @@ -2,7 +2,7 @@ name = "atomicwrites" version = "1.4.0" description = "Atomic file writes." -category = "main" +category = "dev" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" @@ -24,70 +24,38 @@ tests_no_zope = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (> name = "backports.entry-points-selectable" version = "1.1.0" description = "Compatibility shim providing selectable entry points for older implementations" -category = "main" +category = "dev" optional = false python-versions = ">=2.7" -[package.dependencies] -importlib-metadata = {version = "*", markers = "python_version < \"3.8\""} - [package.extras] docs = ["sphinx", "jaraco.packaging (>=8.2)", "rst.linker (>=1.9)"] testing = ["pytest (>=4.6)", "pytest-flake8", "pytest-cov", "pytest-black (>=0.3.7)", "pytest-mypy", "pytest-checkdocs (>=2.4)", "pytest-enabler (>=1.0.1)"] [[package]] -name = "black" -version = "21.9b0" -description = "The uncompromising code formatter." -category = "main" -optional = false -python-versions = ">=3.6.2" - -[package.dependencies] -click = ">=7.1.2" -mypy-extensions = ">=0.4.3" -pathspec = ">=0.9.0,<1" -platformdirs = ">=2" -regex = ">=2020.1.8" -tomli = ">=0.2.6,<2.0.0" -typed-ast = {version = ">=1.4.2", markers = "python_version < \"3.8\""} -typing-extensions = [ - {version = ">=3.10.0.0", markers = "python_version < \"3.10\""}, - {version = "!=3.10.0.1", markers = "python_version >= \"3.10\""}, -] - -[package.extras] -colorama = ["colorama (>=0.4.3)"] -d = ["aiohttp (>=3.6.0)", "aiohttp-cors (>=0.4.0)"] -jupyter = ["ipython (>=7.8.0)", "tokenize-rt (>=3.2.0)"] -python2 = ["typed-ast (>=1.4.2)"] -uvloop = ["uvloop (>=0.15.2)"] - -[[package]] -name = "cfgv" -version = "3.3.1" -description = "Validate configuration and produce human readable error messages." +name = "certifi" +version = "2023.7.22" +description = "Python package for providing Mozilla's CA Bundle." category = "main" optional = false -python-versions = ">=3.6.1" +python-versions = ">=3.6" [[package]] -name = "click" -version = "8.0.3" -description = "Composable command line interface toolkit" +name = "cffi" +version = "1.15.1" +description = "Foreign Function Interface for Python calling C code." category = "main" optional = false -python-versions = ">=3.6" +python-versions = "*" [package.dependencies] -colorama = {version = "*", markers = "platform_system == \"Windows\""} -importlib-metadata = {version = "*", markers = "python_version < \"3.8\""} +pycparser = "*" [[package]] name = "colorama" version = "0.4.4" description = "Cross-platform colored terminal text." -category = "main" +category = "dev" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" @@ -95,15 +63,55 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" name = "distlib" version = "0.3.3" description = "Distribution utilities" +category = "dev" +optional = false +python-versions = "*" + +[[package]] +name = "django-environ" +version = "0.11.1" +description = "A package that allows you to utilize 12factor inspired environment variables to configure your Django application." +category = "main" +optional = false +python-versions = ">=3.6,<4" + +[package.extras] +develop = ["coverage[toml] (>=5.0a4)", "pytest (>=4.6.11)", "furo (>=2021.8.17b43,<2021.9.0)", "sphinx (>=3.5.0)", "sphinx-notfound-page"] +docs = ["furo (>=2021.8.17b43,<2021.9.0)", "sphinx (>=3.5.0)", "sphinx-notfound-page"] +testing = ["coverage[toml] (>=5.0a4)", "pytest (>=4.6.11)"] + +[[package]] +name = "easyprocess" +version = "1.1" +description = "Easy to use Python subprocess interface." category = "main" optional = false python-versions = "*" +[[package]] +name = "entrypoint2" +version = "1.1" +description = "easy to use command-line interface for python modules" +category = "main" +optional = false +python-versions = "*" + +[[package]] +name = "exceptiongroup" +version = "1.1.3" +description = "Backport of PEP 654 (exception groups)" +category = "main" +optional = false +python-versions = ">=3.7" + +[package.extras] +test = ["pytest (>=6)"] + [[package]] name = "filelock" version = "3.3.1" description = "A platform independent file lock." -category = "main" +category = "dev" optional = false python-versions = ">=3.6" @@ -112,62 +120,78 @@ docs = ["furo (>=2021.8.17b43)", "sphinx (>=4.1)", "sphinx-autodoc-typehints (>= testing = ["covdefaults (>=1.2.0)", "coverage (>=4)", "pytest (>=4)", "pytest-cov", "pytest-timeout (>=1.4.2)"] [[package]] -name = "identify" -version = "2.3.1" -description = "File identification library for Python" +name = "h11" +version = "0.14.0" +description = "A pure-Python, bring-your-own-I/O implementation of HTTP/1.1" category = "main" optional = false -python-versions = ">=3.6.1" - -[package.extras] -license = ["editdistance-s"] +python-versions = ">=3.7" [[package]] -name = "importlib-metadata" -version = "4.8.1" -description = "Read metadata from Python packages" +name = "idna" +version = "3.4" +description = "Internationalized Domain Names in Applications (IDNA)" category = "main" optional = false -python-versions = ">=3.6" - -[package.dependencies] -typing-extensions = {version = ">=3.6.4", markers = "python_version < \"3.8\""} -zipp = ">=0.5" - -[package.extras] -docs = ["sphinx", "jaraco.packaging (>=8.2)", "rst.linker (>=1.9)"] -perf = ["ipython"] -testing = ["pytest (>=4.6)", "pytest-checkdocs (>=2.4)", "pytest-flake8", "pytest-cov", "pytest-enabler (>=1.0.1)", "packaging", "pep517", "pyfakefs", "flufl.flake8", "pytest-perf (>=0.9.2)", "pytest-black (>=0.3.7)", "pytest-mypy", "importlib-resources (>=1.3)"] +python-versions = ">=3.5" [[package]] name = "iniconfig" version = "1.1.1" description = "iniconfig: brain-dead simple config-ini parsing" -category = "main" +category = "dev" optional = false python-versions = "*" [[package]] -name = "mypy-extensions" -version = "0.4.3" -description = "Experimental type system extensions for programs checked with the mypy typechecker." +name = "jeepney" +version = "0.8.0" +description = "Low-level, pure Python DBus protocol wrapper." category = "main" optional = false -python-versions = "*" +python-versions = ">=3.7" + +[package.extras] +test = ["pytest", "pytest-trio", "pytest-asyncio (>=0.17)", "testpath", "trio", "async-timeout"] +trio = ["trio", "async-generator"] [[package]] -name = "nodeenv" -version = "1.6.0" -description = "Node.js virtual environment builder" +name = "mouseinfo" +version = "0.1.3" +description = "An application to display XY position and RGB color information for the pixel currently under the mouse. Works on Python 2 and 3." category = "main" optional = false python-versions = "*" +[package.dependencies] +pyperclip = "*" +python3-Xlib = {version = "*", markers = "platform_system == \"Linux\" and python_version >= \"3.0\""} +rubicon-objc = {version = "*", markers = "platform_system == \"Darwin\""} + +[[package]] +name = "mss" +version = "9.0.1" +description = "An ultra fast cross-platform multiple screenshots module in pure python using ctypes." +category = "main" +optional = false +python-versions = ">=3.8" + +[[package]] +name = "outcome" +version = "1.2.0" +description = "Capture the outcome of Python function calls." +category = "main" +optional = false +python-versions = ">=3.7" + +[package.dependencies] +attrs = ">=19.2.0" + [[package]] name = "packaging" version = "21.0" description = "Core utilities for Python packages" -category = "main" +category = "dev" optional = false python-versions = ">=3.6" @@ -175,18 +199,22 @@ python-versions = ">=3.6" pyparsing = ">=2.0.2" [[package]] -name = "pathspec" -version = "0.9.0" -description = "Utility library for gitignore style pattern matching of file paths." +name = "pillow" +version = "10.0.0" +description = "Python Imaging Library (Fork)" category = "main" optional = false -python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7" +python-versions = ">=3.8" + +[package.extras] +docs = ["furo", "olefile", "sphinx (>=2.4)", "sphinx-copybutton", "sphinx-inline-tabs", "sphinx-removed-in", "sphinxext-opengraph"] +tests = ["check-manifest", "coverage", "defusedxml", "markdown2", "olefile", "packaging", "pyroma", "pytest", "pytest-cov", "pytest-timeout"] [[package]] name = "platformdirs" version = "2.4.0" description = "A small Python module for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." -category = "main" +category = "dev" optional = false python-versions = ">=3.6" @@ -198,58 +226,167 @@ test = ["appdirs (==1.4.4)", "pytest (>=6)", "pytest-cov (>=2.7)", "pytest-mock name = "pluggy" version = "1.0.0" description = "plugin and hook calling mechanisms for python" -category = "main" +category = "dev" optional = false python-versions = ">=3.6" -[package.dependencies] -importlib-metadata = {version = ">=0.12", markers = "python_version < \"3.8\""} - [package.extras] dev = ["pre-commit", "tox"] testing = ["pytest", "pytest-benchmark"] [[package]] -name = "pre-commit" -version = "2.15.0" -description = "A framework for managing and maintaining multi-language pre-commit hooks." +name = "py" +version = "1.10.0" +description = "library with cross-python path, ini-parsing, io, code, log facilities" +category = "dev" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" + +[[package]] +name = "pyautogui" +version = "0.9.54" +description = "PyAutoGUI lets Python control the mouse and keyboard, and other GUI automation tasks. For Windows, macOS, and Linux, on Python 3 and 2." category = "main" optional = false -python-versions = ">=3.6.1" +python-versions = "*" [package.dependencies] -cfgv = ">=2.0.0" -identify = ">=1.0.0" -importlib-metadata = {version = "*", markers = "python_version < \"3.8\""} -nodeenv = ">=0.11.1" -pyyaml = ">=5.1" -toml = "*" -virtualenv = ">=20.0.8" +mouseinfo = "*" +pygetwindow = ">=0.0.5" +pymsgbox = "*" +pyobjc-core = {version = "*", markers = "platform_system == \"Darwin\""} +pyobjc-framework-quartz = {version = "*", markers = "platform_system == \"Darwin\""} +pyscreeze = ">=0.1.21" +python3-Xlib = {version = "*", markers = "platform_system == \"Linux\" and python_version >= \"3.0\""} +pytweening = ">=1.0.4" [[package]] -name = "py" -version = "1.10.0" -description = "library with cross-python path, ini-parsing, io, code, log facilities" +name = "pycparser" +version = "2.21" +description = "C parser in Python" category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +[[package]] +name = "pygetwindow" +version = "0.0.9" +description = "A simple, cross-platform module for obtaining GUI information on application's windows." +category = "main" +optional = false +python-versions = "*" + +[package.dependencies] +pyrect = "*" + +[[package]] +name = "pymsgbox" +version = "1.0.9" +description = "A simple, cross-platform, pure Python module for JavaScript-like message boxes." +category = "main" +optional = false +python-versions = "*" + +[[package]] +name = "pyobjc-core" +version = "9.2" +description = "Python<->ObjC Interoperability Module" +category = "main" +optional = false +python-versions = ">=3.7" + +[[package]] +name = "pyobjc-framework-cocoa" +version = "9.2" +description = "Wrappers for the Cocoa frameworks on macOS" +category = "main" +optional = false +python-versions = ">=3.7" + +[package.dependencies] +pyobjc-core = ">=9.2" + +[[package]] +name = "pyobjc-framework-quartz" +version = "9.2" +description = "Wrappers for the Quartz frameworks on macOS" +category = "main" +optional = false +python-versions = ">=3.7" + +[package.dependencies] +pyobjc-core = ">=9.2" +pyobjc-framework-Cocoa = ">=9.2" + [[package]] name = "pyparsing" version = "3.0.2" description = "Python parsing module" -category = "main" +category = "dev" optional = false python-versions = ">=3.6" [package.extras] diagrams = ["jinja2", "railroad-diagrams"] +[[package]] +name = "pyperclip" +version = "1.8.2" +description = "A cross-platform clipboard module for Python. (Only handles plain text for now.)" +category = "main" +optional = false +python-versions = "*" + +[[package]] +name = "pyrect" +version = "0.2.0" +description = "PyRect is a simple module with a Rect class for Pygame-like rectangular areas." +category = "main" +optional = false +python-versions = "*" + +[[package]] +name = "pyscreenshot" +version = "3.1" +description = "python screenshot" +category = "main" +optional = false +python-versions = ">=3.4" + +[package.dependencies] +EasyProcess = "*" +entrypoint2 = "*" +jeepney = {version = "*", markers = "python_version > \"3.4\" and platform_system == \"Linux\""} +mss = {version = "*", markers = "python_version > \"3.4\""} + +[[package]] +name = "pyscreeze" +version = "0.1.29" +description = "A simple, cross-platform screenshot module for Python 2 and 3." +category = "main" +optional = false +python-versions = "*" + +[package.dependencies] +Pillow = [ + {version = ">=9.2.0", markers = "python_version == \"3.10\""}, + {version = ">=9.3.0", markers = "python_version == \"3.11\""}, +] +pyscreenshot = "*" + +[[package]] +name = "pysocks" +version = "1.7.1" +description = "A Python SOCKS client module. See https://github.com/Anorov/PySocks for more information." +category = "main" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" + [[package]] name = "pytest" version = "6.2.5" description = "pytest: simple powerful testing with Python" -category = "main" +category = "dev" optional = false python-versions = ">=3.6" @@ -257,7 +394,6 @@ python-versions = ">=3.6" atomicwrites = {version = ">=1.0", markers = "sys_platform == \"win32\""} attrs = ">=19.2.0" colorama = {version = "*", markers = "sys_platform == \"win32\""} -importlib-metadata = {version = ">=0.12", markers = "python_version < \"3.8\""} iniconfig = "*" packaging = "*" pluggy = ">=0.12,<2.0" @@ -268,79 +404,109 @@ toml = "*" testing = ["argcomplete", "hypothesis (>=3.56)", "mock", "nose", "requests", "xmlschema"] [[package]] -name = "pytest-env" -version = "0.6.2" -description = "py.test plugin that allows you to add environment variables." +name = "python-dotenv" +version = "1.0.0" +description = "Read key-value pairs from a .env file and set them as environment variables" +category = "main" +optional = false +python-versions = ">=3.8" + +[package.extras] +cli = ["click (>=5.0)"] + +[[package]] +name = "python3-xlib" +version = "0.15" +description = "Python3 X Library" category = "main" optional = false python-versions = "*" -[package.dependencies] -pytest = ">=2.6.0" +[[package]] +name = "pytweening" +version = "1.0.7" +description = "A collection of tweening / easing functions." +category = "main" +optional = false +python-versions = "*" [[package]] name = "pyyaml" -version = "6.0" +version = "6.0.1" description = "YAML parser and emitter for Python" category = "main" optional = false python-versions = ">=3.6" [[package]] -name = "regex" -version = "2021.10.23" -description = "Alternative regular expression module, to replace re." +name = "rubicon-objc" +version = "0.4.6" +description = "A bridge between an Objective C runtime environment and Python." category = "main" optional = false -python-versions = "*" +python-versions = ">=3.7" + +[package.extras] +dev = ["pytest (==7.3.0)", "pytest-tldr (==0.2.5)", "setuptools-scm[toml] (==7.1.0)", "tox (==4.4.11)", "pre-commit (==2.21.0)", "pre-commit (==3.2.2)"] +docs = ["furo (==2023.3.27)", "pyenchant (==3.2.2)", "sphinx (==6.1.3)", "sphinx-tabs (==3.4.1)", "sphinx-autobuild (==2021.3.14)", "sphinxcontrib-spelling (==8.0.0)"] [[package]] name = "selenium" -version = "3.141.0" -description = "Python bindings for Selenium" +version = "4.12.0" +description = "" category = "main" optional = false -python-versions = "*" +python-versions = ">=3.8" [package.dependencies] -urllib3 = "*" +certifi = ">=2021.10.8" +trio = ">=0.17,<1.0" +trio-websocket = ">=0.9,<1.0" +urllib3 = {version = ">=1.26,<3", extras = ["socks"]} [[package]] name = "six" version = "1.16.0" description = "Python 2 and 3 compatibility utilities" -category = "main" +category = "dev" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" [[package]] -name = "toml" -version = "0.10.2" -description = "Python Library for Tom's Obvious, Minimal Language" +name = "sniffio" +version = "1.3.0" +description = "Sniff out which async library your code is running under" category = "main" optional = false -python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" +python-versions = ">=3.7" [[package]] -name = "tomli" -version = "1.2.2" -description = "A lil' TOML parser" +name = "sortedcontainers" +version = "2.4.0" +description = "Sorted Containers -- Sorted List, Sorted Dict, Sorted Set" category = "main" optional = false -python-versions = ">=3.6" +python-versions = "*" + +[[package]] +name = "toml" +version = "0.10.2" +description = "Python Library for Tom's Obvious, Minimal Language" +category = "dev" +optional = false +python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" [[package]] name = "tox" version = "3.24.4" description = "tox is a generic virtualenv management and test command line tool" -category = "main" +category = "dev" optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7" [package.dependencies] colorama = {version = ">=0.4.1", markers = "platform_system == \"Windows\""} filelock = ">=3.0.0" -importlib-metadata = {version = ">=0.12", markers = "python_version < \"3.8\""} packaging = ">=14" pluggy = ">=0.12.0" py = ">=1.4.17" @@ -353,39 +519,57 @@ docs = ["pygments-github-lexers (>=0.0.5)", "sphinx (>=2.0.0)", "sphinxcontrib-a testing = ["flaky (>=3.4.0)", "freezegun (>=0.3.11)", "psutil (>=5.6.1)", "pytest (>=4.0.0)", "pytest-cov (>=2.5.1)", "pytest-mock (>=1.10.0)", "pytest-randomly (>=1.0.0)", "pytest-xdist (>=1.22.2)", "pathlib2 (>=2.3.3)"] [[package]] -name = "typed-ast" -version = "1.4.3" -description = "a fork of Python 2 and 3 ast modules with type comment support" +name = "trio" +version = "0.22.2" +description = "A friendly Python library for async concurrency and I/O" category = "main" optional = false -python-versions = "*" +python-versions = ">=3.7" + +[package.dependencies] +attrs = ">=20.1.0" +cffi = {version = ">=1.14", markers = "os_name == \"nt\" and implementation_name != \"pypy\""} +exceptiongroup = {version = ">=1.0.0rc9", markers = "python_version < \"3.11\""} +idna = "*" +outcome = "*" +sniffio = "*" +sortedcontainers = "*" [[package]] -name = "typing-extensions" -version = "3.10.0.2" -description = "Backported and Experimental Type Hints for Python 3.5+" +name = "trio-websocket" +version = "0.10.3" +description = "WebSocket library for Trio" category = "main" optional = false -python-versions = "*" +python-versions = ">=3.7" + +[package.dependencies] +exceptiongroup = "*" +trio = ">=0.11" +wsproto = ">=0.14" [[package]] name = "urllib3" -version = "1.26.7" +version = "2.0.4" description = "HTTP library with thread-safe connection pooling, file post, and more." category = "main" optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, <4" +python-versions = ">=3.7" + +[package.dependencies] +pysocks = {version = ">=1.5.6,<1.5.7 || >1.5.7,<2.0", optional = true, markers = "extra == \"socks\""} [package.extras] -brotli = ["brotlipy (>=0.6.0)"] -secure = ["pyOpenSSL (>=0.14)", "cryptography (>=1.3.4)", "idna (>=2.0.0)", "certifi", "ipaddress"] -socks = ["PySocks (>=1.5.6,!=1.5.7,<2.0)"] +brotli = ["brotli (>=1.0.9)", "brotlicffi (>=0.8.0)"] +secure = ["certifi", "cryptography (>=1.9)", "idna (>=2.0.0)", "pyopenssl (>=17.1.0)", "urllib3-secure-extra"] +socks = ["pysocks (>=1.5.6,!=1.5.7,<2.0)"] +zstd = ["zstandard (>=0.18.0)"] [[package]] name = "virtualenv" version = "20.9.0" description = "Virtual Python Environment builder" -category = "main" +category = "dev" optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7" @@ -393,7 +577,6 @@ python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7" "backports.entry-points-selectable" = ">=1.0.4" distlib = ">=0.3.1,<1" filelock = ">=3.2,<4" -importlib-metadata = {version = ">=0.12", markers = "python_version < \"3.8\""} platformdirs = ">=2,<3" six = ">=1.9.0,<2" @@ -402,21 +585,20 @@ docs = ["proselint (>=0.10.2)", "sphinx (>=3)", "sphinx-argparse (>=0.2.5)", "sp testing = ["coverage (>=4)", "coverage-enable-subprocess (>=1)", "flaky (>=3)", "pytest (>=4)", "pytest-env (>=0.6.2)", "pytest-freezegun (>=0.4.1)", "pytest-mock (>=2)", "pytest-randomly (>=1)", "pytest-timeout (>=1)", "packaging (>=20.0)"] [[package]] -name = "zipp" -version = "3.6.0" -description = "Backport of pathlib-compatible object wrapper for zip files" +name = "wsproto" +version = "1.2.0" +description = "WebSockets state-machine based protocol implementation" category = "main" optional = false -python-versions = ">=3.6" +python-versions = ">=3.7.0" -[package.extras] -docs = ["sphinx", "jaraco.packaging (>=8.2)", "rst.linker (>=1.9)"] -testing = ["pytest (>=4.6)", "pytest-checkdocs (>=2.4)", "pytest-flake8", "pytest-cov", "pytest-enabler (>=1.0.1)", "jaraco.itertools", "func-timeout", "pytest-black (>=0.3.7)", "pytest-mypy"] +[package.dependencies] +h11 = ">=0.9.0,<1" [metadata] lock-version = "1.1" -python-versions = "^3.7" -content-hash = "7c60a86443c2531b6514de7587366270e9e3bbe99c0c742ba0ef2608b62a9703" +python-versions = "^3.10" +content-hash = "58fb4aae862dd682be744e12fac931e3160d607bb844aac2ecbf6ecc2f57af6d" [metadata.files] atomicwrites = [ @@ -431,18 +613,8 @@ attrs = [ {file = "backports.entry_points_selectable-1.1.0-py2.py3-none-any.whl", hash = "sha256:a6d9a871cde5e15b4c4a53e3d43ba890cc6861ec1332c9c2428c92f977192acc"}, {file = "backports.entry_points_selectable-1.1.0.tar.gz", hash = "sha256:988468260ec1c196dab6ae1149260e2f5472c9110334e5d51adcb77867361f6a"}, ] -black = [ - {file = "black-21.9b0-py3-none-any.whl", hash = "sha256:380f1b5da05e5a1429225676655dddb96f5ae8c75bdf91e53d798871b902a115"}, - {file = "black-21.9b0.tar.gz", hash = "sha256:7de4cfc7eb6b710de325712d40125689101d21d25283eed7e9998722cf10eb91"}, -] -cfgv = [ - {file = "cfgv-3.3.1-py2.py3-none-any.whl", hash = "sha256:c6a0883f3917a037485059700b9e75da2464e6c27051014ad85ba6aaa5884426"}, - {file = "cfgv-3.3.1.tar.gz", hash = "sha256:f5a830efb9ce7a445376bb66ec94c638a9787422f96264c98edc6bdeed8ab736"}, -] -click = [ - {file = "click-8.0.3-py3-none-any.whl", hash = "sha256:353f466495adaeb40b6b5f592f9f91cb22372351c84caeb068132442a4518ef3"}, - {file = "click-8.0.3.tar.gz", hash = "sha256:410e932b050f5eed773c4cda94de75971c89cdb3155a72a0831139a79e5ecb5b"}, -] +certifi = [] +cffi = [] colorama = [ {file = "colorama-0.4.4-py2.py3-none-any.whl", hash = "sha256:9f47eda37229f68eee03b24b9748937c7dc3868f906e8ba69fbcbdd3bc5dc3e2"}, {file = "colorama-0.4.4.tar.gz", hash = "sha256:5941b2b48a20143d2267e95b1c2a7603ce057ee39fd88e7329b0c292aa16869b"}, @@ -451,38 +623,31 @@ distlib = [ {file = "distlib-0.3.3-py2.py3-none-any.whl", hash = "sha256:c8b54e8454e5bf6237cc84c20e8264c3e991e824ef27e8f1e81049867d861e31"}, {file = "distlib-0.3.3.zip", hash = "sha256:d982d0751ff6eaaab5e2ec8e691d949ee80eddf01a62eaa96ddb11531fe16b05"}, ] +django-environ = [] +easyprocess = [] +entrypoint2 = [] +exceptiongroup = [] filelock = [ {file = "filelock-3.3.1-py3-none-any.whl", hash = "sha256:2b5eb3589e7fdda14599e7eb1a50e09b4cc14f34ed98b8ba56d33bfaafcbef2f"}, {file = "filelock-3.3.1.tar.gz", hash = "sha256:34a9f35f95c441e7b38209775d6e0337f9a3759f3565f6c5798f19618527c76f"}, ] -identify = [ - {file = "identify-2.3.1-py2.py3-none-any.whl", hash = "sha256:5a5000bd3293950d992843c0ef3d82b90a582de2161557bda7f493c8c8864f26"}, - {file = "identify-2.3.1.tar.gz", hash = "sha256:8a92c56893e9a4ce951f09a50489986615e3eba7b4c60610e0b25f93ca4487ba"}, -] -importlib-metadata = [ - {file = "importlib_metadata-4.8.1-py3-none-any.whl", hash = "sha256:b618b6d2d5ffa2f16add5697cf57a46c76a56229b0ed1c438322e4e95645bd15"}, - {file = "importlib_metadata-4.8.1.tar.gz", hash = "sha256:f284b3e11256ad1e5d03ab86bb2ccd6f5339688ff17a4d797a0fe7df326f23b1"}, -] +h11 = [] +idna = [] iniconfig = [ {file = "iniconfig-1.1.1-py2.py3-none-any.whl", hash = "sha256:011e24c64b7f47f6ebd835bb12a743f2fbe9a26d4cecaa7f53bc4f35ee9da8b3"}, {file = "iniconfig-1.1.1.tar.gz", hash = "sha256:bc3af051d7d14b2ee5ef9969666def0cd1a000e121eaea580d4a313df4b37f32"}, ] -mypy-extensions = [ - {file = "mypy_extensions-0.4.3-py2.py3-none-any.whl", hash = "sha256:090fedd75945a69ae91ce1303b5824f428daf5a028d2f6ab8a299250a846f15d"}, - {file = "mypy_extensions-0.4.3.tar.gz", hash = "sha256:2d82818f5bb3e369420cb3c4060a7970edba416647068eb4c5343488a6c604a8"}, -] -nodeenv = [ - {file = "nodeenv-1.6.0-py2.py3-none-any.whl", hash = "sha256:621e6b7076565ddcacd2db0294c0381e01fd28945ab36bcf00f41c5daf63bef7"}, - {file = "nodeenv-1.6.0.tar.gz", hash = "sha256:3ef13ff90291ba2a4a7a4ff9a979b63ffdd00a464dbe04acf0ea6471517a4c2b"}, +jeepney = [] +mouseinfo = [ + {file = "MouseInfo-0.1.3.tar.gz", hash = "sha256:2c62fb8885062b8e520a3cce0a297c657adcc08c60952eb05bc8256ef6f7f6e7"}, ] +mss = [] +outcome = [] packaging = [ {file = "packaging-21.0-py3-none-any.whl", hash = "sha256:c86254f9220d55e31cc94d69bade760f0847da8000def4dfe1c6b872fd14ff14"}, {file = "packaging-21.0.tar.gz", hash = "sha256:7dc96269f53a4ccec5c0670940a4281106dd0bb343f47b7471f779df49c2fbe7"}, ] -pathspec = [ - {file = "pathspec-0.9.0-py2.py3-none-any.whl", hash = "sha256:7d15c4ddb0b5c802d161efc417ec1a2558ea2653c2e8ad9c19098201dc1c993a"}, - {file = "pathspec-0.9.0.tar.gz", hash = "sha256:e564499435a2673d586f6b2130bb5b95f04a3ba06f81b8f895b651a3c76aabb1"}, -] +pillow = [] platformdirs = [ {file = "platformdirs-2.4.0-py3-none-any.whl", hash = "sha256:8868bbe3c3c80d42f20156f22e7131d2fb321f5bc86a2a345375c6481a67021d"}, {file = "platformdirs-2.4.0.tar.gz", hash = "sha256:367a5e80b3d04d2428ffa76d33f124cf11e8fff2acdaa9b43d545f5c7d661ef2"}, @@ -491,164 +656,66 @@ pluggy = [ {file = "pluggy-1.0.0-py2.py3-none-any.whl", hash = "sha256:74134bbf457f031a36d68416e1509f34bd5ccc019f0bcc952c7b909d06b37bd3"}, {file = "pluggy-1.0.0.tar.gz", hash = "sha256:4224373bacce55f955a878bf9cfa763c1e360858e330072059e10bad68531159"}, ] -pre-commit = [ - {file = "pre_commit-2.15.0-py2.py3-none-any.whl", hash = "sha256:a4ed01000afcb484d9eb8d504272e642c4c4099bbad3a6b27e519bd6a3e928a6"}, - {file = "pre_commit-2.15.0.tar.gz", hash = "sha256:3c25add78dbdfb6a28a651780d5c311ac40dd17f160eb3954a0c59da40a505a7"}, -] py = [ {file = "py-1.10.0-py2.py3-none-any.whl", hash = "sha256:3b80836aa6d1feeaa108e046da6423ab8f6ceda6468545ae8d02d9d58d18818a"}, {file = "py-1.10.0.tar.gz", hash = "sha256:21b81bda15b66ef5e1a777a21c4dcd9c20ad3efd0b3f817e7a809035269e1bd3"}, ] +pyautogui = [] +pycparser = [] +pygetwindow = [ + {file = "PyGetWindow-0.0.9.tar.gz", hash = "sha256:17894355e7d2b305cd832d717708384017c1698a90ce24f6f7fbf0242dd0a688"}, +] +pymsgbox = [ + {file = "PyMsgBox-1.0.9.tar.gz", hash = "sha256:2194227de8bff7a3d6da541848705a155dcbb2a06ee120d9f280a1d7f51263ff"}, +] +pyobjc-core = [] +pyobjc-framework-cocoa = [] +pyobjc-framework-quartz = [] pyparsing = [ {file = "pyparsing-3.0.2-py3-none-any.whl", hash = "sha256:09fa60a2e3bc3c6a23c2ed23332199d0e57b2c676b9193750cfc22a4358b69c2"}, {file = "pyparsing-3.0.2.tar.gz", hash = "sha256:9366e57ad2d2faa8dd89775ef284ecae0fc4012c24717f3705613b262d712090"}, ] +pyperclip = [ + {file = "pyperclip-1.8.2.tar.gz", hash = "sha256:105254a8b04934f0bc84e9c24eb360a591aaf6535c9def5f29d92af107a9bf57"}, +] +pyrect = [] +pyscreenshot = [] +pyscreeze = [] +pysocks = [] pytest = [ {file = "pytest-6.2.5-py3-none-any.whl", hash = "sha256:7310f8d27bc79ced999e760ca304d69f6ba6c6649c0b60fb0e04a4a77cacc134"}, {file = "pytest-6.2.5.tar.gz", hash = "sha256:131b36680866a76e6781d13f101efb86cf674ebb9762eb70d3082b6f29889e89"}, ] -pytest-env = [ - {file = "pytest-env-0.6.2.tar.gz", hash = "sha256:7e94956aef7f2764f3c147d216ce066bf6c42948bb9e293169b1b1c880a580c2"}, -] -pyyaml = [ - {file = "PyYAML-6.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d4db7c7aef085872ef65a8fd7d6d09a14ae91f691dec3e87ee5ee0539d516f53"}, - {file = "PyYAML-6.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9df7ed3b3d2e0ecfe09e14741b857df43adb5a3ddadc919a2d94fbdf78fea53c"}, - {file = "PyYAML-6.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:77f396e6ef4c73fdc33a9157446466f1cff553d979bd00ecb64385760c6babdc"}, - {file = "PyYAML-6.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a80a78046a72361de73f8f395f1f1e49f956c6be882eed58505a15f3e430962b"}, - {file = "PyYAML-6.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:f84fbc98b019fef2ee9a1cb3ce93e3187a6df0b2538a651bfb890254ba9f90b5"}, - {file = "PyYAML-6.0-cp310-cp310-win32.whl", hash = "sha256:2cd5df3de48857ed0544b34e2d40e9fac445930039f3cfe4bcc592a1f836d513"}, - {file = "PyYAML-6.0-cp310-cp310-win_amd64.whl", hash = "sha256:daf496c58a8c52083df09b80c860005194014c3698698d1a57cbcfa182142a3a"}, - {file = "PyYAML-6.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:897b80890765f037df3403d22bab41627ca8811ae55e9a722fd0392850ec4d86"}, - {file = "PyYAML-6.0-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:50602afada6d6cbfad699b0c7bb50d5ccffa7e46a3d738092afddc1f9758427f"}, - {file = "PyYAML-6.0-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:48c346915c114f5fdb3ead70312bd042a953a8ce5c7106d5bfb1a5254e47da92"}, - {file = "PyYAML-6.0-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:98c4d36e99714e55cfbaaee6dd5badbc9a1ec339ebfc3b1f52e293aee6bb71a4"}, - {file = "PyYAML-6.0-cp36-cp36m-win32.whl", hash = "sha256:0283c35a6a9fbf047493e3a0ce8d79ef5030852c51e9d911a27badfde0605293"}, - {file = "PyYAML-6.0-cp36-cp36m-win_amd64.whl", hash = "sha256:07751360502caac1c067a8132d150cf3d61339af5691fe9e87803040dbc5db57"}, - {file = "PyYAML-6.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:819b3830a1543db06c4d4b865e70ded25be52a2e0631ccd2f6a47a2822f2fd7c"}, - {file = "PyYAML-6.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:473f9edb243cb1935ab5a084eb238d842fb8f404ed2193a915d1784b5a6b5fc0"}, - {file = "PyYAML-6.0-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0ce82d761c532fe4ec3f87fc45688bdd3a4c1dc5e0b4a19814b9009a29baefd4"}, - {file = "PyYAML-6.0-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:231710d57adfd809ef5d34183b8ed1eeae3f76459c18fb4a0b373ad56bedcdd9"}, - {file = "PyYAML-6.0-cp37-cp37m-win32.whl", hash = "sha256:c5687b8d43cf58545ade1fe3e055f70eac7a5a1a0bf42824308d868289a95737"}, - {file = "PyYAML-6.0-cp37-cp37m-win_amd64.whl", hash = "sha256:d15a181d1ecd0d4270dc32edb46f7cb7733c7c508857278d3d378d14d606db2d"}, - {file = "PyYAML-6.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:0b4624f379dab24d3725ffde76559cff63d9ec94e1736b556dacdfebe5ab6d4b"}, - {file = "PyYAML-6.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:213c60cd50106436cc818accf5baa1aba61c0189ff610f64f4a3e8c6726218ba"}, - {file = "PyYAML-6.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9fa600030013c4de8165339db93d182b9431076eb98eb40ee068700c9c813e34"}, - {file = "PyYAML-6.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:277a0ef2981ca40581a47093e9e2d13b3f1fbbeffae064c1d21bfceba2030287"}, - {file = "PyYAML-6.0-cp38-cp38-win32.whl", hash = "sha256:d4eccecf9adf6fbcc6861a38015c2a64f38b9d94838ac1810a9023a0609e1b78"}, - {file = "PyYAML-6.0-cp38-cp38-win_amd64.whl", hash = "sha256:1e4747bc279b4f613a09eb64bba2ba602d8a6664c6ce6396a4d0cd413a50ce07"}, - {file = "PyYAML-6.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:055d937d65826939cb044fc8c9b08889e8c743fdc6a32b33e2390f66013e449b"}, - {file = "PyYAML-6.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:e61ceaab6f49fb8bdfaa0f92c4b57bcfbea54c09277b1b4f7ac376bfb7a7c174"}, - {file = "PyYAML-6.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d67d839ede4ed1b28a4e8909735fc992a923cdb84e618544973d7dfc71540803"}, - {file = "PyYAML-6.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cba8c411ef271aa037d7357a2bc8f9ee8b58b9965831d9e51baf703280dc73d3"}, - {file = "PyYAML-6.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:40527857252b61eacd1d9af500c3337ba8deb8fc298940291486c465c8b46ec0"}, - {file = "PyYAML-6.0-cp39-cp39-win32.whl", hash = "sha256:b5b9eccad747aabaaffbc6064800670f0c297e52c12754eb1d976c57e4f74dcb"}, - {file = "PyYAML-6.0-cp39-cp39-win_amd64.whl", hash = "sha256:b3d267842bf12586ba6c734f89d1f5b871df0273157918b0ccefa29deb05c21c"}, - {file = "PyYAML-6.0.tar.gz", hash = "sha256:68fb519c14306fec9720a2a5b45bc9f0c8d1b9c72adf45c37baedfcd949c35a2"}, -] -regex = [ - {file = "regex-2021.10.23-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:45b65d6a275a478ac2cbd7fdbf7cc93c1982d613de4574b56fd6972ceadb8395"}, - {file = "regex-2021.10.23-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:74d071dbe4b53c602edd87a7476ab23015a991374ddb228d941929ad7c8c922e"}, - {file = "regex-2021.10.23-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:34d870f9f27f2161709054d73646fc9aca49480617a65533fc2b4611c518e455"}, - {file = "regex-2021.10.23-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2fb698037c35109d3c2e30f2beb499e5ebae6e4bb8ff2e60c50b9a805a716f79"}, - {file = "regex-2021.10.23-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:cb46b542133999580ffb691baf67410306833ee1e4f58ed06b6a7aaf4e046952"}, - {file = "regex-2021.10.23-cp310-cp310-win32.whl", hash = "sha256:5e9c9e0ce92f27cef79e28e877c6b6988c48b16942258f3bc55d39b5f911df4f"}, - {file = "regex-2021.10.23-cp310-cp310-win_amd64.whl", hash = "sha256:ab7c5684ff3538b67df3f93d66bd3369b749087871ae3786e70ef39e601345b0"}, - {file = "regex-2021.10.23-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:de557502c3bec8e634246588a94e82f1ee1b9dfcfdc453267c4fb652ff531570"}, - {file = "regex-2021.10.23-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ee684f139c91e69fe09b8e83d18b4d63bf87d9440c1eb2eeb52ee851883b1b29"}, - {file = "regex-2021.10.23-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:5095a411c8479e715784a0c9236568ae72509450ee2226b649083730f3fadfc6"}, - {file = "regex-2021.10.23-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7b568809dca44cb75c8ebb260844ea98252c8c88396f9d203f5094e50a70355f"}, - {file = "regex-2021.10.23-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:eb672217f7bd640411cfc69756ce721d00ae600814708d35c930930f18e8029f"}, - {file = "regex-2021.10.23-cp36-cp36m-win32.whl", hash = "sha256:a7a986c45d1099a5de766a15de7bee3840b1e0e1a344430926af08e5297cf666"}, - {file = "regex-2021.10.23-cp36-cp36m-win_amd64.whl", hash = "sha256:6d7722136c6ed75caf84e1788df36397efdc5dbadab95e59c2bba82d4d808a4c"}, - {file = "regex-2021.10.23-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:9f665677e46c5a4d288ece12fdedf4f4204a422bb28ff05f0e6b08b7447796d1"}, - {file = "regex-2021.10.23-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:450dc27483548214314640c89a0f275dbc557968ed088da40bde7ef8fb52829e"}, - {file = "regex-2021.10.23-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:129472cd06062fb13e7b4670a102951a3e655e9b91634432cfbdb7810af9d710"}, - {file = "regex-2021.10.23-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a940ca7e7189d23da2bfbb38973832813eab6bd83f3bf89a977668c2f813deae"}, - {file = "regex-2021.10.23-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:530fc2bbb3dc1ebb17f70f7b234f90a1dd43b1b489ea38cea7be95fb21cdb5c7"}, - {file = "regex-2021.10.23-cp37-cp37m-win32.whl", hash = "sha256:ded0c4a3eee56b57fcb2315e40812b173cafe79d2f992d50015f4387445737fa"}, - {file = "regex-2021.10.23-cp37-cp37m-win_amd64.whl", hash = "sha256:391703a2abf8013d95bae39145d26b4e21531ab82e22f26cd3a181ee2644c234"}, - {file = "regex-2021.10.23-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:be04739a27be55631069b348dda0c81d8ea9822b5da10b8019b789e42d1fe452"}, - {file = "regex-2021.10.23-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:13ec99df95003f56edcd307db44f06fbeb708c4ccdcf940478067dd62353181e"}, - {file = "regex-2021.10.23-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:8d1cdcda6bd16268316d5db1038965acf948f2a6f43acc2e0b1641ceab443623"}, - {file = "regex-2021.10.23-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0c186691a7995ef1db61205e00545bf161fb7b59cdb8c1201c89b333141c438a"}, - {file = "regex-2021.10.23-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:2b20f544cbbeffe171911f6ce90388ad36fe3fad26b7c7a35d4762817e9ea69c"}, - {file = "regex-2021.10.23-cp38-cp38-win32.whl", hash = "sha256:c0938ddd60cc04e8f1faf7a14a166ac939aac703745bfcd8e8f20322a7373019"}, - {file = "regex-2021.10.23-cp38-cp38-win_amd64.whl", hash = "sha256:56f0c81c44638dfd0e2367df1a331b4ddf2e771366c4b9c5d9a473de75e3e1c7"}, - {file = "regex-2021.10.23-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:80bb5d2e92b2258188e7dcae5b188c7bf868eafdf800ea6edd0fbfc029984a88"}, - {file = "regex-2021.10.23-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e1dae12321b31059a1a72aaa0e6ba30156fe7e633355e445451e4021b8e122b6"}, - {file = "regex-2021.10.23-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:1f2b59c28afc53973d22e7bc18428721ee8ca6079becf1b36571c42627321c65"}, - {file = "regex-2021.10.23-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d134757a37d8640f3c0abb41f5e68b7cf66c644f54ef1cb0573b7ea1c63e1509"}, - {file = "regex-2021.10.23-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:0dcc0e71118be8c69252c207630faf13ca5e1b8583d57012aae191e7d6d28b84"}, - {file = "regex-2021.10.23-cp39-cp39-win32.whl", hash = "sha256:a30513828180264294953cecd942202dfda64e85195ae36c265daf4052af0464"}, - {file = "regex-2021.10.23-cp39-cp39-win_amd64.whl", hash = "sha256:0f7552429dd39f70057ac5d0e897e5bfe211629652399a21671e53f2a9693a4e"}, - {file = "regex-2021.10.23.tar.gz", hash = "sha256:f3f9a91d3cc5e5b0ddf1043c0ae5fa4852f18a1c0050318baf5fc7930ecc1f9c"}, -] -selenium = [ - {file = "selenium-3.141.0-py2.py3-none-any.whl", hash = "sha256:2d7131d7bc5a5b99a2d9b04aaf2612c411b03b8ca1b1ee8d3de5845a9be2cb3c"}, - {file = "selenium-3.141.0.tar.gz", hash = "sha256:deaf32b60ad91a4611b98d8002757f29e6f2c2d5fcaf202e1c9ad06d6772300d"}, +python-dotenv = [] +python3-xlib = [ + {file = "python3-xlib-0.15.tar.gz", hash = "sha256:dc4245f3ae4aa5949c1d112ee4723901ade37a96721ba9645f2bfa56e5b383f8"}, ] +pytweening = [] +pyyaml = [] +rubicon-objc = [] +selenium = [] six = [ {file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"}, {file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"}, ] +sniffio = [] +sortedcontainers = [ + {file = "sortedcontainers-2.4.0-py2.py3-none-any.whl", hash = "sha256:a163dcaede0f1c021485e957a39245190e74249897e2ae4b2aa38595db237ee0"}, + {file = "sortedcontainers-2.4.0.tar.gz", hash = "sha256:25caa5a06cc30b6b83d11423433f65d1f9d76c4c6a0c90e3379eaa43b9bfdb88"}, +] toml = [ {file = "toml-0.10.2-py2.py3-none-any.whl", hash = "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b"}, {file = "toml-0.10.2.tar.gz", hash = "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f"}, ] -tomli = [ - {file = "tomli-1.2.2-py3-none-any.whl", hash = "sha256:f04066f68f5554911363063a30b108d2b5a5b1a010aa8b6132af78489fe3aade"}, - {file = "tomli-1.2.2.tar.gz", hash = "sha256:c6ce0015eb38820eaf32b5db832dbc26deb3dd427bd5f6556cf0acac2c214fee"}, -] tox = [ {file = "tox-3.24.4-py2.py3-none-any.whl", hash = "sha256:5e274227a53dc9ef856767c21867377ba395992549f02ce55eb549f9fb9a8d10"}, {file = "tox-3.24.4.tar.gz", hash = "sha256:c30b57fa2477f1fb7c36aa1d83292d5c2336cd0018119e1b1c17340e2c2708ca"}, ] -typed-ast = [ - {file = "typed_ast-1.4.3-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:2068531575a125b87a41802130fa7e29f26c09a2833fea68d9a40cf33902eba6"}, - {file = "typed_ast-1.4.3-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:c907f561b1e83e93fad565bac5ba9c22d96a54e7ea0267c708bffe863cbe4075"}, - {file = "typed_ast-1.4.3-cp35-cp35m-manylinux2014_aarch64.whl", hash = "sha256:1b3ead4a96c9101bef08f9f7d1217c096f31667617b58de957f690c92378b528"}, - {file = "typed_ast-1.4.3-cp35-cp35m-win32.whl", hash = "sha256:dde816ca9dac1d9c01dd504ea5967821606f02e510438120091b84e852367428"}, - {file = "typed_ast-1.4.3-cp35-cp35m-win_amd64.whl", hash = "sha256:777a26c84bea6cd934422ac2e3b78863a37017618b6e5c08f92ef69853e765d3"}, - {file = "typed_ast-1.4.3-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:f8afcf15cc511ada719a88e013cec87c11aff7b91f019295eb4530f96fe5ef2f"}, - {file = "typed_ast-1.4.3-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:52b1eb8c83f178ab787f3a4283f68258525f8d70f778a2f6dd54d3b5e5fb4341"}, - {file = "typed_ast-1.4.3-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:01ae5f73431d21eead5015997ab41afa53aa1fbe252f9da060be5dad2c730ace"}, - {file = "typed_ast-1.4.3-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:c190f0899e9f9f8b6b7863debfb739abcb21a5c054f911ca3596d12b8a4c4c7f"}, - {file = "typed_ast-1.4.3-cp36-cp36m-win32.whl", hash = "sha256:398e44cd480f4d2b7ee8d98385ca104e35c81525dd98c519acff1b79bdaac363"}, - {file = "typed_ast-1.4.3-cp36-cp36m-win_amd64.whl", hash = "sha256:bff6ad71c81b3bba8fa35f0f1921fb24ff4476235a6e94a26ada2e54370e6da7"}, - {file = "typed_ast-1.4.3-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:0fb71b8c643187d7492c1f8352f2c15b4c4af3f6338f21681d3681b3dc31a266"}, - {file = "typed_ast-1.4.3-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:760ad187b1041a154f0e4d0f6aae3e40fdb51d6de16e5c99aedadd9246450e9e"}, - {file = "typed_ast-1.4.3-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:5feca99c17af94057417d744607b82dd0a664fd5e4ca98061480fd8b14b18d04"}, - {file = "typed_ast-1.4.3-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:95431a26309a21874005845c21118c83991c63ea800dd44843e42a916aec5899"}, - {file = "typed_ast-1.4.3-cp37-cp37m-win32.whl", hash = "sha256:aee0c1256be6c07bd3e1263ff920c325b59849dc95392a05f258bb9b259cf39c"}, - {file = "typed_ast-1.4.3-cp37-cp37m-win_amd64.whl", hash = "sha256:9ad2c92ec681e02baf81fdfa056fe0d818645efa9af1f1cd5fd6f1bd2bdfd805"}, - {file = "typed_ast-1.4.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:b36b4f3920103a25e1d5d024d155c504080959582b928e91cb608a65c3a49e1a"}, - {file = "typed_ast-1.4.3-cp38-cp38-manylinux1_i686.whl", hash = "sha256:067a74454df670dcaa4e59349a2e5c81e567d8d65458d480a5b3dfecec08c5ff"}, - {file = "typed_ast-1.4.3-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:7538e495704e2ccda9b234b82423a4038f324f3a10c43bc088a1636180f11a41"}, - {file = "typed_ast-1.4.3-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:af3d4a73793725138d6b334d9d247ce7e5f084d96284ed23f22ee626a7b88e39"}, - {file = "typed_ast-1.4.3-cp38-cp38-win32.whl", hash = "sha256:f2362f3cb0f3172c42938946dbc5b7843c2a28aec307c49100c8b38764eb6927"}, - {file = "typed_ast-1.4.3-cp38-cp38-win_amd64.whl", hash = "sha256:dd4a21253f42b8d2b48410cb31fe501d32f8b9fbeb1f55063ad102fe9c425e40"}, - {file = "typed_ast-1.4.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:f328adcfebed9f11301eaedfa48e15bdece9b519fb27e6a8c01aa52a17ec31b3"}, - {file = "typed_ast-1.4.3-cp39-cp39-manylinux1_i686.whl", hash = "sha256:2c726c276d09fc5c414693a2de063f521052d9ea7c240ce553316f70656c84d4"}, - {file = "typed_ast-1.4.3-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:cae53c389825d3b46fb37538441f75d6aecc4174f615d048321b716df2757fb0"}, - {file = "typed_ast-1.4.3-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:b9574c6f03f685070d859e75c7f9eeca02d6933273b5e69572e5ff9d5e3931c3"}, - {file = "typed_ast-1.4.3-cp39-cp39-win32.whl", hash = "sha256:209596a4ec71d990d71d5e0d312ac935d86930e6eecff6ccc7007fe54d703808"}, - {file = "typed_ast-1.4.3-cp39-cp39-win_amd64.whl", hash = "sha256:9c6d1a54552b5330bc657b7ef0eae25d00ba7ffe85d9ea8ae6540d2197a3788c"}, - {file = "typed_ast-1.4.3.tar.gz", hash = "sha256:fb1bbeac803adea29cedd70781399c99138358c26d05fcbd23c13016b7f5ec65"}, -] -typing-extensions = [ - {file = "typing_extensions-3.10.0.2-py2-none-any.whl", hash = "sha256:d8226d10bc02a29bcc81df19a26e56a9647f8b0a6d4a83924139f4a8b01f17b7"}, - {file = "typing_extensions-3.10.0.2-py3-none-any.whl", hash = "sha256:f1d25edafde516b146ecd0613dabcc61409817af4766fbbcfb8d1ad4ec441a34"}, - {file = "typing_extensions-3.10.0.2.tar.gz", hash = "sha256:49f75d16ff11f1cd258e1b988ccff82a3ca5570217d7ad8c5f48205dd99a677e"}, -] -urllib3 = [ - {file = "urllib3-1.26.7-py2.py3-none-any.whl", hash = "sha256:c4fdf4019605b6e5423637e01bc9fe4daef873709a7973e195ceba0a62bbc844"}, - {file = "urllib3-1.26.7.tar.gz", hash = "sha256:4987c65554f7a2dbf30c18fd48778ef124af6fab771a377103da0585e2336ece"}, -] +trio = [] +trio-websocket = [] +urllib3 = [] virtualenv = [ {file = "virtualenv-20.9.0-py2.py3-none-any.whl", hash = "sha256:1d145deec2da86b29026be49c775cc5a9aab434f85f7efef98307fb3965165de"}, {file = "virtualenv-20.9.0.tar.gz", hash = "sha256:bb55ace18de14593947354e5e6cd1be75fb32c3329651da62e92bf5d0aab7213"}, ] -zipp = [ - {file = "zipp-3.6.0-py3-none-any.whl", hash = "sha256:9fe5ea21568a0a70e50f273397638d39b03353731e6cbbb3fd8502a33fec40bc"}, - {file = "zipp-3.6.0.tar.gz", hash = "sha256:71c644c5369f4a6e07636f0aa966270449561fcea2e3d6747b8d23efaa9d7832"}, -] +wsproto = [] diff --git a/pyproject.toml b/pyproject.toml index d75eb42..0082da1 100755 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,7 +1,6 @@ [tool.black] -line-length = 100 -target-version = ['py36', "py37", "py38",] -experimental_string_processing = true +line-length = 120 +target-version = ['py311'] include = '\.pyi?$' exclude = ''' /( @@ -17,13 +16,20 @@ exclude = ''' )/ ''' +# ==== isort ==== [tool.isort] -profile = 'black' -multi_line_output = 3 +profile = "black" +line_length = 120 +known_first_party = [ + "config", + "apps", +] +skip = [] +skip_glob = [] [tool.poetry] name = "simpleselenium" -version = "0.1.0" +version = "0.2.0" description = "Python package to easily work with Selenium." authors = ["Vishal Kumar Mishra "] license = "MIT" @@ -36,12 +42,13 @@ include = [ ] [tool.poetry.dependencies] -python = "^3.7" -selenium = "3.141.0" -pre-commit = "^2.15.0" -black = "^21.9b0" -tox = "^3.24.4" -pytest-env = "^0.6.2" +python = "^3.10" +selenium = "^4.12.0" +PyAutoGUI = "^0.9.54" +django-environ = "^0.11.1" +python-dotenv = "^1.0.0" +PyYAML = "^6.0.1" + [tool.poetry.dev-dependencies] pytest = "^6.2.5" @@ -50,3 +57,70 @@ tox = "^3.24.4" [build-system] requires = ["poetry-core>=1.0.0"] build-backend = "poetry.core.masonry.api" + + +# ==== pytest ==== +[tool.pytest.ini_options] +minversion = "6.0" +addopts = "--ds=config.settings --reuse-db" +python_files = [ + "tests.py", + "test_*.py", +] + +# ==== mypy ==== +[tool.mypy] +python_version = "3.11" +check_untyped_defs = true +ignore_missing_imports = true +warn_unused_ignores = true +warn_redundant_casts = true +warn_unused_configs = true + + +[tool.ruff] +# Enable pycodestyle (`E`) and Pyflakes (`F`) codes by default. +select = ["E", "F"] +ignore = [] + +# Allow autofix for all enabled rules (when `--fix`) is provided. +fixable = ["A", "B", "C", "D", "E", "F", "G", "I", "N", "Q", "S", "T", "W", "ANN", "ARG", "BLE", "COM", "DJ", "DTZ", "EM", "ERA", "EXE", "FBT", "ICN", "INP", "ISC", "NPY", "PD", "PGH", "PIE", "PL", "PT", "PTH", "PYI", "RET", "RSE", "RUF", "SIM", "SLF", "TCH", "TID", "TRY", "UP", "YTT"] +unfixable = [] + +# Exclude a variety of commonly ignored directories. +exclude = [ + ".bzr", + ".direnv", + ".eggs", + ".git", + ".git-rewrite", + ".hg", + ".mypy_cache", + ".nox", + ".pants.d", + ".pytype", + ".ruff_cache", + ".svn", + ".tox", + ".venv", + "__pypackages__", + "_build", + "buck-out", + "build", + "dist", + "node_modules", + "venv", +] + +line-length = 120 +dummy-variable-rgx = "^(_+|(_+[a-zA-Z0-9_]*[a-zA-Z0-9]+?))$" +target-version = "py310" + +[tool.ruff.mccabe] +# Unlike Flake8, default to a complexity level of 10. +max-complexity = 10 + +[tool.bandit] +exclude_dirs = [".tox"] +# tests = ["B201", "B301"] +skips = ["B113"] diff --git a/read_envs.py b/read_envs.py new file mode 100644 index 0000000..e0c1c49 --- /dev/null +++ b/read_envs.py @@ -0,0 +1,9 @@ +"""Read .env file""" +from pathlib import Path + +from dotenv import load_dotenv + + +def read_environment_vars(filename: str | Path = ".env"): + env_file_path = Path(filename).resolve() + load_dotenv(env_file_path) diff --git a/requirements.txt b/requirements.txt deleted file mode 100755 index 538d9f8..0000000 --- a/requirements.txt +++ /dev/null @@ -1,25 +0,0 @@ -appdirs==1.4.4 -backports.entry-points-selectable==1.1.0 -black==21.6b0 -cfgv==3.3.1 -click==8.0.3 -distlib==0.3.3 -filelock==3.3.1 -identify==2.3.1 -mypy-extensions==0.4.3 -nodeenv==1.6.0 -packaging==21.0 -pathspec==0.9.0 -platformdirs==2.4.0 -pluggy==1.0.0 -pre-commit==2.13.0 -py==1.10.0 -pyparsing==3.0.2 -PyYAML==6.0 -regex==2021.10.23 -selenium==3.141.0 -six==1.16.0 -toml==0.10.2 -tox==3.24.4 -urllib3==1.26.7 -virtualenv==20.9.0 diff --git a/requirements/base.txt b/requirements/base.txt new file mode 100644 index 0000000..0b5dc18 --- /dev/null +++ b/requirements/base.txt @@ -0,0 +1,5 @@ +selenium==4.12.0 +PyAutoGUI==0.9.54 + +django-environ +python-dotenv diff --git a/requirements/dev.txt b/requirements/dev.txt new file mode 100644 index 0000000..d79d233 --- /dev/null +++ b/requirements/dev.txt @@ -0,0 +1,10 @@ +-r base.txt + +# Development related +pre-commit +black +ruff +sourcery +mypy +bandit +tox diff --git a/settings.py b/settings.py new file mode 100644 index 0000000..4f72e7e --- /dev/null +++ b/settings.py @@ -0,0 +1,48 @@ +# https://github.com/pjialin/django-environ + +import logging.config +from pathlib import Path + +import environ +import yaml + +BASE_DIR = Path(__file__).parent + +LOG_CONF_FILE = "log_config.yaml" +ENV_FILE = ".env" + + +# ======== +# LOGGING +# ======== + +# Usage: +# from settings import get_logger +# logger = get_logger(__name__) +# logger.log("message") + +with open(BASE_DIR / LOG_CONF_FILE) as f: + config = yaml.safe_load(f.read()) + logging.config.dictConfig(config) + + +def getLogger(name): # noqa + return logging.getLogger(name) + + +get_logger = getLogger + +# =========================== +# ENVIRONMENT VARIABLE UTILS +# =========================== + +env = environ.Env() + +env_file = BASE_DIR / Path(ENV_FILE) +env.read_env(env_file=env_file) + +# ============================ +# GLOBAL ENVIRONMENT VARIABLES +# ============================ + +# ENV_VAR = env("ENV_VAR") diff --git a/scripts/sample_script.py b/simpleselenium/sample_script.py similarity index 100% rename from scripts/sample_script.py rename to simpleselenium/sample_script.py diff --git a/simpleselenium/scripts.py b/simpleselenium/scripts.py new file mode 100644 index 0000000..da16de4 --- /dev/null +++ b/simpleselenium/scripts.py @@ -0,0 +1,7 @@ +PAGE_HEIGHT = "return document.documentElement.scrollHeight" + +ELEMENT_ATTRIBUTES = ( + "var items = {}; for (index = 0; index < arguments[0].attributes.length; ++index) {" + " items[arguments[0].attributes[index].name] = arguments[0].attributes[index].value };" + " return items;" +) diff --git a/simpleselenium/selenium_requests.py b/simpleselenium/selenium_requests.py index f057fda..fbd13b7 100755 --- a/simpleselenium/selenium_requests.py +++ b/simpleselenium/selenium_requests.py @@ -1,16 +1,23 @@ -from __future__ import annotations - -import os +import contextlib import time from collections import OrderedDict +import pyautogui from selenium import webdriver from selenium.webdriver.chrome.options import Options as ChromeOptions +from selenium.webdriver.common.by import By from selenium.webdriver.common.keys import Keys from selenium.webdriver.firefox.options import Options as FirefoxOptions +from selenium.webdriver.remote import webelement from selenium.webdriver.support import expected_conditions as EC from selenium.webdriver.support.ui import WebDriverWait +from simpleselenium import scripts + + +class SeleniumRequestException(Exception): + pass + class Session: """A top level class to manage a browser containing one/more Tabs""" @@ -20,7 +27,7 @@ class Session: "FireFox": webdriver.Firefox, } - BROWSER_OPTION_FUNCTION = {"Chrome": ChromeOptions, "Firefox": FirefoxOptions} + BROWSER_OPTION_FUNCTION = {"Chrome": ChromeOptions, "FireFox": FirefoxOptions} def __init__(self, browser_name, driver_path, implicit_wait, user_agent, headless=False): self.browser = browser_name @@ -33,8 +40,7 @@ def __init__(self, browser_name, driver_path, implicit_wait, user_agent, headles self.driver = self._get_driver() def _get_driver(self) -> webdriver: - """returns the driver/browser instance based on set environment variables - and arguments""" + """returns the driver/browser instance based on set variables and arguments""" driver_options = self.BROWSER_OPTION_FUNCTION[self.browser]() @@ -46,25 +52,13 @@ def _get_driver(self) -> webdriver: driver_options.add_argument("no-default-browser-check") driver = self.BROWSER_DRIVER_FUNCTION[self.browser]( - self.driver_path, options=driver_options + options=driver_options, ) driver.implicitly_wait(self.implicit_wait) # driver.set_page_load_timeout(self.implicit_wait) return driver - def _get_attr_or_env(self, env_var, raise_exception=True): - if hasattr(self, env_var): - return getattr(self, env_var) - - driver_path = os.getenv(env_var, None) - - if driver_path: - return os.getenv(env_var, None) - - if raise_exception: - raise Exception(f"Please define {env_var} as class attribute or environment variable.") - def close(self): """Close Session""" self.__del__() @@ -83,14 +77,11 @@ def __init__(self, session, tab_handle, start_url: str = None): def __str__(self): return ( - "Tab(" - f"start_url={self.start_url}, " - f"active={self.is_active}, " - f"alive={self.is_alive}, " - f"tab_handle={self.tab_handle}" - ")" + f"Tab(start_url={self.start_url}, active={self.is_active}, alive={self.is_alive}, handle={self.tab_handle})" ) + __repr__ = __str__ + @property def is_alive(self): """Whether the tab is one of the browser tabs""" @@ -101,7 +92,7 @@ def is_active(self): """Whether the tab is active tab on the browser""" try: return self._session.driver.current_window_handle == self.tab_handle - except: # noqa + except Exception: # noqa return False @property @@ -114,57 +105,81 @@ def url(self) -> str: """Returns the title of the page at the moment""" return self.driver.current_url + @property + def page_source(self) -> str: + return self.driver.page_source + @property def driver(self) -> webdriver: """Switch to tab (if possible) and return driver""" if not self.is_alive: - raise Exception("Current window is dead.") + raise SeleniumRequestException("Current window is dead.") if not self.is_active: self._session.driver.switch_to.window(self.tab_handle) return self._session.driver - def switch(self) -> None: + def switch(self) -> bool: """Switch to tab (if possible)""" - if self.is_alive: - self._session.driver.switch_to.window(self.tab_handle) - else: - raise Exception( + if self.is_active: + # no need to switch + return False + + if not self.is_alive: + raise SeleniumRequestException( f"Current window is dead. Window Handle={self.tab_handle} does not exist" f" in all currently open window handles: {self._session.driver.window_handles}" ) + self._session.driver.switch_to.window(self.tab_handle) + return True + def open(self, url): """Open a url in the tab""" self.driver.get(url) return self - def click(self, element): + def click(self, element) -> bool: """Click a given element on the page represented by the tab""" try: self.switch() element.click() + return True except Exception as e: # noqa try: self.driver.execute_script("arguments[0].click();", element) + return True except Exception as e: # noqa - pass + return False + + @staticmethod + def element_source(element: webelement): + return element.get_attribute("outerHTML") + + @staticmethod + def element_location(element: webelement) -> dict: + return element.location + + @staticmethod + def element_size(element: webelement) -> dict: + return element.size + + @staticmethod + def element_center(element): + pass + + def run_js(self, script, *args): + """Run JavaScript on the page""" + return self.driver.execute_script(script, *args) def get_all_attributes_of_element(self, element) -> dict: """Get all attributes of a given element on the tab's page""" - attr_dict = self.driver.execute_script( - "var items = {}; for (index = 0; index < arguments[0].attributes.length; ++index) {" - " items[arguments[0].attributes[index].name] = arguments[0].attributes[index].value };" - " return items;", - element, - ) - - return attr_dict + return self.run_js(scripts.ELEMENT_ATTRIBUTES, element) def get_attribute(self, element, attr_name): """Get specific attributes of a given element on the tab's page""" @@ -181,44 +196,51 @@ def find_element(self, by, value, multiple=False): return elements elif elements: if len(elements) > 1: - raise Exception("Multiple elements found") + raise SeleniumRequestException("Multiple elements found") else: return elements[0] else: return None - def scroll(self, times=3, wait=1): + @staticmethod + def _scroll(clicks: int = 10, times: int = 1, direction=1): """Usual scroll""" + # TODO: May not work in all the situations and OS' + for _ in range(times): - self.scroll_to_bottom(wait=wait) + pyautogui.scroll(direction * clicks) + time.sleep(0.5) + + def scroll_up(self, times: int = 1, clicks: int = 20): + self.switch() + self._scroll(clicks=clicks, times=times, direction=1) - def scroll_to_bottom(self, wait=1): - """Scroll to bottom of the page""" + def scroll_down(self, times: int = 1, clicks: int = 20): + self.switch() + self._scroll(clicks=clicks, times=times, direction=-1) + + def scroll_to_bottom(self): + """Scroll to the bottom of the page""" html = self.driver.find_element_by_tag_name("html") html.send_keys(Keys.END) - time.sleep(wait) def infinite_scroll(self, retries=5): """Infinite (so many times) scroll""" for _ in range(max(1, retries)): - try: + with contextlib.suppress(Exception): # noqa last_height = 0 while True: - self.scroll() - new_height = self.driver.execute_script( - "return document.documentElement.scrollHeight" - ) + self.scroll_to_bottom() + new_height = self.run_js(scripts.PAGE_HEIGHT) if new_height == last_height: break last_height = new_height - except Exception as e: # noqa - pass def wait_for_presence_of_element(self, element, wait): return WebDriverWait(self.driver, wait).until(EC.presence_of_element_located(element)) @@ -241,12 +263,10 @@ def wait_for_presence_and_visibility(self, by, key, wait): self.wait_for_visibility(by, key, wait) return ele - def wait_until_staleness(self, element, wait=5): + def wait_until_staleness(self, element, wait: int = 5): """Wait until the passed element is no longer present on the page""" WebDriverWait(self.driver, wait).until(EC.staleness_of(element)) - time.sleep(0.5) - class TabManager: """A manager for multiple tabs associated with a browser""" @@ -276,9 +296,9 @@ def current_tab(self) -> [Tab, None]: return self.get(tab_handle) def get_blank_tab(self) -> Tab: - """Get a blank tab to work with. Switchs to the newly created tab""" + """Get a blank tab to work with. Switches to the newly created tab""" windows_before = self.driver.current_window_handle - self.driver.execute_script("""window.open('{}');""".format("about:blank")) + self.driver.execute_script("window.open('about:blank');") windows_after = self.driver.window_handles new_window = [x for x in windows_after if x != windows_before][-1] self.driver.switch_to.window(new_window) @@ -286,19 +306,26 @@ def get_blank_tab(self) -> Tab: new_tab.switch() return new_tab - def open_new_tab(self, url): - """Open a new tab with with a given URL""" + def open_new_tab(self, url, wait_for_tag="body", wait_sec=30): + """Open a new tab with a given URL. + + It also waits for a specified number of seconds for the specified tag to appear on the page""" blank_tab = self.get_blank_tab() - blank_tab.start_url = url # Not a recommended way to update object attributes + blank_tab.start_url = url + blank_tab.switch() blank_tab.open(url) + + if wait_for_tag: + blank_tab.wait_for_presence_and_visibility(by=By.TAG_NAME, key=wait_for_tag, wait=wait_sec) + return blank_tab def all(self): """All tabs of the browser""" curr_tab = self.current_tab() - all_tabs = [tab for tab in self._all_tabs.values()] + all_tabs = list(self._all_tabs.values()) if curr_tab: curr_tab.switch() return all_tabs @@ -324,7 +351,7 @@ def exist(self, tab: Tab) -> bool: if isinstance(tab, Tab): return tab.tab_handle in self._all_tabs.keys() - raise Exception("Invalid type for tab.") + raise SeleniumRequestException("Invalid type for tab.") def remove(self, tab: Tab) -> [Tab, None]: """Remove a tab from the list of tabs""" @@ -332,37 +359,43 @@ def remove(self, tab: Tab) -> [Tab, None]: if isinstance(tab, Tab): return self._all_tabs.pop(tab.tab_handle, None) - raise Exception("Invalid type for tab.") + raise SeleniumRequestException("Invalid type for tab.") + @property def first_tab(self) -> Tab | None: """First tab from the list of tabs of the browser""" try: _, tab = list(self._all_tabs.items())[0] return tab - except: # noqa + except Exception: # noqa return None + @property def last_tab(self) -> Tab | None: """Last tab from the list of tabs of the browser""" try: _, tab = list(self._all_tabs.items())[-1] return tab - except: # noqa + except Exception: # noqa return None def switch_to_first_tab(self): - """Switch to the first tab""" + """Attempts to switch to the first tab""" - first_tab = self.first_tab() - if first_tab and first_tab.is_alive and self.exist(first_tab): - first_tab.switch() + if self.first_tab and self.first_tab.is_alive and self.exist(self.first_tab): + self.first_tab.switch() + return True + + return False def switch_to_last_tab(self): """Switch to the last tab""" - last_tab = self.last_tab() - if last_tab and last_tab.is_alive and self.exist(last_tab): - last_tab.switch() + if self.last_tab and self.last_tab.is_alive and self.exist(self.last_tab): + self.last_tab.switch() + return True + + return False class Browser: @@ -376,16 +409,12 @@ class Browser: "FireFox": "FIREFOX_DRIVER_PATH", } - def __init__( - self, name, driver_path=None, implicit_wait: int = 0, user_agent: str = "", headless=False - ): + def __init__(self, name, driver_path=None, implicit_wait: int = 0, user_agent: str = "", headless=False): self.name = name - self.implicit_wait = implicit_wait or self._get_attr_or_env("IMPLICIT_WAIT_TIME") - self.user_agent = user_agent or self._get_attr_or_env( - "SELENIUM_USER_AGENT", raise_exception=False - ) - self.driver_path = driver_path or self.get_driver_path() + self.implicit_wait = implicit_wait + self.user_agent = user_agent + self.driver_path = driver_path self._session = Session( name, @@ -394,7 +423,7 @@ def __init__( implicit_wait=self.implicit_wait, user_agent=self.user_agent, ) - self.tabs = TabManager(self._session) + self._tabs = TabManager(self._session) def __enter__(self): return self @@ -402,95 +431,101 @@ def __enter__(self): def __exit__(self, exc_type, exc_val, exc_tb): self.close() - def get_driver_path(self): - """Get path of the driver from env or settings""" - - driver_env = self.BROWSER_DRIVER_PATH_ENV[self.name] - return self._get_attr_or_env(driver_env) + @property + def tabs(self) -> list: + """Returns all open tabs""" + return self._tabs.all() - def get_current_tab(self): + @property + def current_tab(self) -> Tab: """get current tab from the list of the tabs""" - return self.tabs.current_tab() - - def _get_attr_or_env(self, env, raise_exception=True): - if hasattr(self, env): - return getattr(self, env) - - driver_path = os.getenv(env, None) + return self._tabs.current_tab() - if driver_path: - return os.getenv(env, None) + @property + def first_tab(self) -> Tab: + return self._tabs.first_tab - if raise_exception: - raise Exception(f"Please define {env} as class attribute or environment variable.") + @property + def last_tab(self) -> Tab: + return self._tabs.last_tab def open(self, url): """Starts a new tab with the given url at end of list of tabs.""" - self.tabs.switch_to_last_tab() - curr_tab = self.tabs.open_new_tab(url) + + self._tabs.switch_to_last_tab() + curr_tab = self._tabs.open_new_tab(url) curr_tab.switch() return curr_tab - def get_all_tabs(self) -> list: - return self.tabs.all() - def _remove_tab(self, tab: Tab): - """For Internal Use Only: + """For Internal Use Only: Closes a given tab. The order of operation is extremely important here. Practice extreme caution while editing this.""" - assert tab.is_alive is True - assert self.tabs.exist(tab) is True + + assert tab.is_alive is True # noqa # nosec + assert self._tabs.exist(tab) is True # noqa # nosec + tab.switch() - self.tabs.remove(tab) + self._tabs.remove(tab) self._session.driver.close() - assert tab.is_alive is False - assert self.tabs.exist(tab) is False + + assert tab.is_alive is False # noqa # nosec + assert self._tabs.exist(tab) is False # noqa # nosec def close_tab(self, tab: Tab): """Close a given tab""" - if self.tabs.exist(tab): + if self._tabs.exist(tab): tab.switch() self._remove_tab(tab=tab) - - self.tabs.switch_to_last_tab() + self._tabs.switch_to_last_tab() return True else: - raise Exception("Tab does not exist.") + raise SeleniumRequestException("Tab does not exist.") def close(self): """Close browser""" - self.tabs = {} + self._tabs = {} self._session.close() if __name__ == "__main__": - chrome_driver = r"/Users/dayhatt/workspace/drivers/chromedriver" - - with Browser(name="Chrome", driver_path=chrome_driver, implicit_wait=10) as browser: - google = browser.open("https://google.com") + with Browser(name="Chrome", driver_path=None, implicit_wait=10) as browser: + google = browser.open("https://google.com") # a `Tab` object yahoo = browser.open("https://yahoo.com") bing = browser.open("https://bing.com") duck_duck = browser.open("https://duckduckgo.com/") - print(browser.get_all_tabs()) + yahoo.scroll_up(times=5) + yahoo.scroll_down(times=10) + + print(browser.tabs) + print(browser.current_tab) + print(browser.first_tab) + print(browser.last_tab) + + print(browser.last_tab.switch()) + + print(google.page_source) + print(google.title) + print(google.url) + print(google.is_active) + print(google.is_alive) browser.close_tab(bing) - print(browser.get_all_tabs()) + print(browser.tabs) - print(browser.get_current_tab()) - time.sleep(5) + print(browser.current_tab) yahoo.switch() - print(browser.get_current_tab()) - time.sleep(5) - + print(browser.current_tab) google.switch() - print(browser.get_current_tab()) - time.sleep(5) + + print(browser.current_tab) browser.close_tab(yahoo) - time.sleep(5) - print(google.driver.title, google.title) + print(yahoo.is_alive) + print(yahoo.is_active) - print(browser.get_all_tabs()) + print(google.driver.title, google.title) + print(google.driver.title == google.title) diff --git a/tests/test_basic_function.py b/tests/test_basic_function.py index 3c4f5f0..1eafed8 100644 --- a/tests/test_basic_function.py +++ b/tests/test_basic_function.py @@ -10,11 +10,11 @@ def test_run_without_exception(): bing = browser.open("https://bing.com") duck_duck = browser.open("https://duckduckgo.com/") # noqa - assert len(browser.get_all_tabs()) == 4 + assert len(browser.all_tabs()) == 4 browser.close_tab(bing) - assert len(browser.get_all_tabs()) == 3 + assert len(browser.all_tabs()) == 3 yahoo.switch() diff --git a/tox.ini b/tox.ini index a8ad200..4399ab4 100644 --- a/tox.ini +++ b/tox.ini @@ -1,7 +1,7 @@ [tox] ;skipsdist = True isolated_build = True -envlist = py39 +envlist = py310 [testenv] whitelist_externals = poetry diff --git a/utility_scripts/bandit_util.sh b/utility_scripts/bandit_util.sh new file mode 100644 index 0000000..df4a85a --- /dev/null +++ b/utility_scripts/bandit_util.sh @@ -0,0 +1,5 @@ +( + cd .. + bandit -c bandit.yaml -r $PWD + exit 0 +) diff --git a/utility_scripts/black_util.sh b/utility_scripts/black_util.sh new file mode 100644 index 0000000..9c6b225 --- /dev/null +++ b/utility_scripts/black_util.sh @@ -0,0 +1,4 @@ +( + cd .. + black . +) diff --git a/utility_scripts/codelimit_util.sh b/utility_scripts/codelimit_util.sh new file mode 100644 index 0000000..fd01374 --- /dev/null +++ b/utility_scripts/codelimit_util.sh @@ -0,0 +1,3 @@ +# https://github.com/getcodelimit/codelimit + +codelimit check $PWD diff --git a/utility_scripts/mypy_util.sh b/utility_scripts/mypy_util.sh new file mode 100644 index 0000000..e0be498 --- /dev/null +++ b/utility_scripts/mypy_util.sh @@ -0,0 +1,6 @@ +( + cd .. + + mypy . + +) diff --git a/utility_scripts/pre_commit_util.sh b/utility_scripts/pre_commit_util.sh new file mode 100644 index 0000000..736aeed --- /dev/null +++ b/utility_scripts/pre_commit_util.sh @@ -0,0 +1,7 @@ +( + cd .. + + pre-commit run --all-files + + exit 0 +) diff --git a/utility_scripts/ruff_util.sh b/utility_scripts/ruff_util.sh new file mode 100644 index 0000000..9d4b282 --- /dev/null +++ b/utility_scripts/ruff_util.sh @@ -0,0 +1,7 @@ +( + cd .. + + # ruff -h + + ruff . --fix +) diff --git a/utility_scripts/sourcery_utils.sh b/utility_scripts/sourcery_utils.sh new file mode 100644 index 0000000..e3dc889 --- /dev/null +++ b/utility_scripts/sourcery_utils.sh @@ -0,0 +1,8 @@ +# https://docs.sourcery.ai/Guides/Getting-Started/Command-Line/ +# https://docs.sourcery.ai/Reference/Configuration/Inline/#skip-a-specific-refactoring + +( + cd .. + + sourcery review . +)