├── .coverage ├── .coveragerc ├── .env1 ├── .github └── workflows │ ├── build.yml │ ├── codecov.yml │ └── codeql-analysis.yml ├── LICENSE ├── README.md ├── conftest.py ├── coverage.xml ├── deepl_tr_async ├── __init__.py ├── __main__.py ├── deepl_langpair.py ├── deepl_tr_async.py ├── detect_lang.py ├── google_langpair.py ├── google_tr_async.py └── load_env.py ├── img ├── copyfrom-false.png └── helpfull.png ├── mypy.ini ├── poetry.lock ├── pyproject - Copy.toml ├── pyproject.toml ├── pyrightconfig.json ├── pytest.ini ├── requirements.txt └── tests ├── __init__.py ├── test_deepl_tr_async.py ├── test_detect_lang.py └── test_google_tr_async.py /.coverage: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ffreemt/deepl-tr-async/d71e5f87c922f99bb40312e286accdf3c76013cb/.coverage -------------------------------------------------------------------------------- /.coveragerc: -------------------------------------------------------------------------------- 1 | # .coveragerc to control coverage.py 2 | [run] 3 | branch = True 4 | omit = 5 | */__main__.py 6 | [report] 7 | exclude_lines = 8 | pragma: no cover 9 | def __repr__ 10 | if self.debug: 11 | if settings.DEBUG 12 | raise AssertionError 13 | raise NotImplementedError 14 | if 0: 15 | if __name__ == .__main__.: 16 | except Exception: 17 | except Exception as exc: 18 | def main(): 19 | [paths] 20 | source = 21 | deepl-tr-async -------------------------------------------------------------------------------- /.env1: -------------------------------------------------------------------------------- 1 | export HEADFUL=1 # 1: show the browser 2 | # export PROXY=SOCKS5://127.0.0.1:1080 3 | export DEBUG=1 -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | # poetry build 2 | # poetry publish -u ${{ secrets.PYPI_USERNAME }} -p ${{ secrets.PYPI_PASSWORD }} 3 | # export POETRY_PYPI_TOKEN=my-token 4 | # export POETRY_HTTP_BASIC_PYPI_USERNAME=username 5 | # export POETRY_HTTP_BASIC_PYPI_PASSWORD=password 6 | name: build 7 | 8 | on: [push] 9 | 10 | jobs: 11 | build: 12 | 13 | runs-on: ubuntu-latest 14 | strategy: 15 | max-parallel: 4 16 | matrix: 17 | python-version: [3.7, 3.8] 18 | # python-version: [3.6] 19 | 20 | steps: 21 | - uses: actions/checkout@v1 22 | - name: Set up Python ${{ matrix.python-version }} 23 | uses: actions/setup-python@v1 24 | with: 25 | python-version: ${{ matrix.python-version }} 26 | - name: Install dependencies 27 | run: | 28 | sudo apt-get install libicu-dev 29 | # python3 -m pip install --upgrade pip 30 | python -m pip install --upgrade pip 31 | python -m pip install poetry 32 | poetry install -v 33 | - name: Lint with flake8 34 | run: | 35 | poetry run python -m flake8 . --count --exit-zero --max-complexity=35 --ignore=E501,F401,E302,W292,F811,F841 --statistics 36 | 37 | - name: Test with pytest 38 | run: | 39 | poetry run python -m pytest tests 40 | 41 | - name: Build Python package 42 | run: | 43 | poetry build 44 | 45 | - name: Publish to PyPI 46 | env: 47 | POETRY_HTTP_BASIC_PYPI_USERNAME: ${{ secrets.PYPI_USER }} 48 | POETRY_HTTP_BASIC_PYPI_PASSWORD: ${{ secrets.PYPI_PASSWORD }} 49 | # POETRY_PYPI_TOKEN_PYPI: ${{ secrets.PYPI_TOKENV }} 50 | run: | 51 | # poetry publish -v -------------------------------------------------------------------------------- /.github/workflows/codecov.yml: -------------------------------------------------------------------------------- 1 | name: Codecov 2 | on: [push] 3 | jobs: 4 | run: 5 | runs-on: ${{ matrix.os }} 6 | strategy: 7 | matrix: 8 | os: [ubuntu-latest] 9 | python-version: [3.7] 10 | steps: 11 | - uses: actions/checkout@master 12 | - name: Setup Python 13 | uses: actions/setup-python@master 14 | with: 15 | python-version: ${{ matrix.python-version }} 16 | - name: Install dependencies # https://molecule.readthedocs.io/en/latest/testing.html 17 | run: | 18 | sudo apt-get install libicu-dev 19 | python -m pip install --upgrade pip 20 | python -m pip install poetry 21 | poetry install -v 22 | poetry run python -m pytest -v --cov=deepl_tr_async --cov-report=xml --cov-report=term --cov-report=html 23 | - name: Upload coverage to Codecov 24 | uses: codecov/codecov-action@v1.0.5 25 | with: 26 | token: ${{ secrets.CODECOV_TOKEN }} 27 | file: ./coverage.xml 28 | flags: unittests 29 | name: codecov-umbrella 30 | yml: ./codecov.yml 31 | fail_ci_if_error: true 32 | -------------------------------------------------------------------------------- /.github/workflows/codeql-analysis.yml: -------------------------------------------------------------------------------- 1 | # For most projects, this workflow file will not need changing; you simply need 2 | # to commit it to your repository. 3 | # 4 | # You may wish to alter this file to override the set of languages analyzed, 5 | # or to provide custom queries or build logic. 6 | # 7 | # ******** NOTE ******** 8 | # We have attempted to detect the languages in your repository. Please check 9 | # the `language` matrix defined below to confirm you have the correct set of 10 | # supported CodeQL languages. 11 | # 12 | name: "CodeQL" 13 | 14 | on: 15 | push: 16 | branches: [ master ] 17 | pull_request: 18 | # The branches below must be a subset of the branches above 19 | branches: [ master ] 20 | schedule: 21 | - cron: '34 1 * * 6' 22 | 23 | jobs: 24 | analyze: 25 | name: Analyze 26 | runs-on: ubuntu-latest 27 | 28 | strategy: 29 | fail-fast: false 30 | matrix: 31 | language: [ 'python' ] 32 | # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python' ] 33 | # Learn more: 34 | # https://docs.github.com/en/free-pro-team@latest/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#changing-the-languages-that-are-analyzed 35 | 36 | steps: 37 | - name: Checkout repository 38 | uses: actions/checkout@v2 39 | 40 | # Initializes the CodeQL tools for scanning. 41 | - name: Initialize CodeQL 42 | uses: github/codeql-action/init@v1 43 | with: 44 | languages: ${{ matrix.language }} 45 | # If you wish to specify custom queries, you can do so here or in a config file. 46 | # By default, queries listed here will override any specified in a config file. 47 | # Prefix the list here with "+" to use these queries and those in the config file. 48 | # queries: ./path/to/local/query, your-org/your-repo/queries@main 49 | 50 | # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). 51 | # If this step fails, then you should remove it and run the build manually (see below) 52 | - name: Autobuild 53 | uses: github/codeql-action/autobuild@v1 54 | 55 | # ℹ️ Command-line programs to run using the OS shell. 56 | # 📚 https://git.io/JvXDl 57 | 58 | # ✏️ If the Autobuild fails above, remove it and uncomment the following three lines 59 | # and modify them (or add more) to build your code if your project 60 | # uses a compiled language 61 | 62 | #- run: | 63 | # make bootstrap 64 | # make release 65 | 66 | - name: Perform CodeQL Analysis 67 | uses: github/codeql-action/analyze@v1 68 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 ffreemt 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # deepl-tr-async [![python](https://img.shields.io/static/v1?label=python+&message=3.7%2B&color=blue)](https://img.shields.io/static/v1?label=python+&message=3.7%2B&color=blue)[![build](https://github.com/ffreemt/deepl-tr-async/actions/workflows/build.yml/badge.svg)](https://github.com/ffreemt/deepl-tr-async/actions/workflows/build.yml)[![codecov](https://codecov.io/gh/ffreemt/deepl-tr-async/branch/master/graph/badge.svg)](https://codecov.io/gh/ffreemt/deepl-tr-async)[![PyPI version](https://badge.fury.io/py/deepl-tr-async.svg)](https://badge.fury.io/py/deepl-tr-async) 2 | 3 | deepl translate for free with async and proxy support, based on pyppeteer 4 | 5 | ## Changes in v0.0.5 6 | * Python 3.6 is no longer supported. 7 | * `get_ppbrowser` is now an indepent package that `deepl-tr-async` depents on. 8 | 9 | ## Pre-installation of libicu 10 | 11 | ### For Linux/OSX 12 | 13 | E.g. 14 | * Ubuntu: `sudo apt install libicu-dev` 15 | * Centos: `yum install libicu` 16 | * OSX: `brew install icu4c` 17 | 18 | ### For Windows 19 | 20 | Download and install the pyicu and pycld2 whl packages for your OS version from https://www.lfd.uci.edu/~gohlke/pythonlibs/#pyicu and https://www.lfd.uci.edu/~gohlke/pythonlibs/#pycld2 21 | 22 | ## Installation 23 | ```pip install deepl-tr-async``` 24 | 25 | Validate installation 26 | ``` 27 | python -c "import deepl_tr_async; print(deepl_tr_async.__version__)" 28 | # 0.0.2 or other version info 29 | ``` 30 | 31 | ## Usage 32 | 33 | ### from the command line 命令行调用 34 | * translate the system clipboad (not tested in Linux) 翻译系统剪贴板 35 | `deepl-tr` 36 | * translate text supplied from the command line 翻译终端提供的句子 37 | `deepl-tr --copyfrom=false this is a test` 38 | 39 | ![img](https://raw.githubusercontent.com/ffreemt/deepl-tr-async/master/img/copyfrom-false.png) 40 | * Help 帮助: 41 | 42 | `deepl-tr -?` 43 | 44 | or 45 | 46 | `deepl-tr --helpfull` 47 | 48 | ![img](https://raw.githubusercontent.com/ffreemt/deepl-tr-async/master/img/helpfull.png) 49 | 50 | ### Programmatic use 程序调用 51 | ``` 52 | import asyncio 53 | from deepl_tr_async import deepl_tr_async 54 | from deepl_tr_async.google_tr_async import google_tr_async 55 | 56 | loop = asyncio.get_event_loop() 57 | 58 | sent = 'Global coronavirus pandemic kills more than 30,000' 59 | 60 | res = loop.run_until_complete(deepl_tr_async(sent, to_lang='zh')) 61 | print(res) 62 | # Alternatives: 63 | # 全球冠状病毒大流行导致超过3万人死亡 64 | # 全球冠状病毒大流行导致3万多人死亡 65 | # 全球冠状病毒大流行导致超过30,000人死亡 66 | # 全球冠状病毒大流行导致3万多人丧生 67 | 68 | res = loop.run_until_complete(google_tr_async(sent, to_lang='zh')) 69 | print(res) 70 | # 全球冠状病毒大流行杀死超过30,000人 71 | 72 | tasks = [deepl_tr_async(sent, to_lang='zh'), google_tr_async(sent, to_lang='zh')] 73 | _ = asyncio.gather(*tasks) 74 | res = loop.run_until_complete(_) 75 | print(res) 76 | ['Alternatives:\n全球冠状病毒大流行导致超过3万人死亡\n全球冠状病毒大流行导致3万多人死亡\n全球冠状病毒大流行导致超过30,000人死亡\n全球冠状病毒大流行导致3万多人丧生', '全球冠状病毒大流行杀死超过30,000人'] 77 | ``` 78 | 79 | ## Environment variables: PPBROWSER_HEADFUL, PPBROWSER_DEBUG, PPBROWSER_PROXY 80 | This version of `deep-tr-async` makes use of the package `get-ppbrowser`. `get-ppbrowser` is a headless browser based on `pyppeteer2`. 81 | 82 | To turn off headless mode, i.e., to show the browser in action, set PPBROWSER_HEADFUL to 1 (or true or True) in the `.env` file, e.g., 83 | ```bash 84 | PPBROWSER_HEADFUL=1 85 | ``` 86 | 87 | or from the cmomand line, e.g., 88 | ```bash 89 | set PPBROWSER_HEADFUL=1 90 | # export PPBROWSER_HEADFUL=1 in linux or iOS 91 | ``` 92 | 93 | or in a python script 94 | ```python 95 | import os 96 | 97 | os.environ["PPBROWSER_HEADFUL"]="1" # note the quotes 98 | ``` 99 | 100 | PPBROWSER_DEBUG and PPBROWSER_PROXY can be set in a similar manner. 101 | -------------------------------------------------------------------------------- /conftest.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ffreemt/deepl-tr-async/d71e5f87c922f99bb40312e286accdf3c76013cb/conftest.py -------------------------------------------------------------------------------- /coverage.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | C:\dl\Dropbox\mat-dir\myapps\pypi-projects\deepl-tr-async-free\deepl_tr_async 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | 221 | 222 | 223 | 224 | 225 | 226 | 227 | 228 | 229 | 230 | 231 | 232 | 233 | 234 | 235 | 236 | 237 | 238 | 239 | 240 | 241 | 242 | 243 | 244 | 245 | 246 | 247 | 248 | 249 | 250 | 251 | 252 | 253 | 254 | 255 | 256 | 257 | 258 | 259 | 260 | 261 | 262 | 263 | 264 | 265 | 266 | 267 | 268 | 269 | 270 | 271 | 272 | 273 | 274 | 275 | 276 | 277 | 278 | 279 | 280 | 281 | 282 | 283 | 284 | 285 | 286 | 287 | 288 | 289 | 290 | 291 | 292 | 293 | 294 | 295 | 296 | 297 | 298 | 299 | 300 | 301 | 302 | 303 | 304 | 305 | 306 | 307 | 308 | 309 | 310 | 311 | 312 | 313 | 314 | 315 | 316 | 317 | 318 | 319 | 320 | 321 | 322 | 323 | 324 | 325 | 326 | 327 | 328 | 329 | 330 | 331 | 332 | 333 | 334 | 335 | 336 | 337 | 338 | 339 | 340 | 341 | 342 | 343 | 344 | 345 | 346 | 347 | 348 | 349 | 350 | 351 | 352 | 353 | 354 | 355 | 356 | 357 | 358 | 359 | 360 | 361 | 362 | 363 | 364 | 365 | 366 | 367 | 368 | 369 | 370 | 371 | 372 | 373 | 374 | 375 | 376 | 377 | 378 | 379 | 380 | 381 | 382 | 383 | 384 | 385 | 386 | 387 | 388 | 389 | 390 | 391 | 392 | 393 | 394 | 395 | 396 | 397 | 398 | 399 | 400 | 401 | 402 | 403 | 404 | 405 | 406 | 407 | 408 | 409 | 410 | 411 | 412 | 413 | 414 | 415 | 416 | 417 | 418 | 419 | 420 | 421 | 422 | 423 | 424 | 425 | 426 | 427 | 428 | 429 | 430 | 431 | 432 | 433 | 434 | 435 | 436 | 437 | 438 | 439 | 440 | 441 | 442 | 443 | 444 | 445 | 446 | 447 | 448 | 449 | 450 | 451 | 452 | -------------------------------------------------------------------------------- /deepl_tr_async/__init__.py: -------------------------------------------------------------------------------- 1 | """ deepl_tr_async via pyppeteer and proxy support """ 2 | # from deepl_tr_async.deepl_tr_async import deepl_tr_async, BROWSER, get_ppbrowser, HEADFUL, DEBUG # noqa: F401 3 | from deepl_tr_async.deepl_tr_async import deepl_tr_async # noqa: F401 4 | # from deepl_tr_async.google_langpair import google_langpair 5 | 6 | # version__ = """0.0.1""" 7 | # date__ = "2020.3.24" 8 | __version__ = """0.0.5""" 9 | __date__ = "2021.2.27" 10 | VERSION = tuple(__version__.split(".")) 11 | -------------------------------------------------------------------------------- /deepl_tr_async/__main__.py: -------------------------------------------------------------------------------- 1 | r""" deepl translate via pyppeteer 2 | for de/fr/ja/it/x as third language 3 | """ 4 | import asyncio 5 | import pyperclip 6 | from absl import app, flags 7 | from textwrap import fill 8 | import logzero 9 | from logzero import logger 10 | 11 | from deepl_tr_async.deepl_tr_async import main 12 | from deepl_tr_async import deepl_tr_async 13 | from deepl_tr_async.google_tr_async import google_tr_async 14 | from deepl_tr_async.detect_lang import detect_lang 15 | from get_ppbrowser import LOOP 16 | 17 | FLAGS = flags.FLAGS 18 | flags.DEFINE_string( 19 | 'z-extra-info', 20 | 'info', 21 | 'supply text anywhere in the command line when --copyfrom=false', 22 | ) 23 | flags.DEFINE_string( 24 | # 'from-lang', 25 | 'mother-lang', 26 | 'zh', 27 | 'mother tongue language, default chinese)', 28 | short_name='m' 29 | ) 30 | flags.DEFINE_string( 31 | # 'to-lang', 32 | 'second-lang', 33 | 'en', 34 | 'second language, default english', 35 | short_name='s' 36 | ) 37 | flags.DEFINE_string( 38 | # 'to-lang', 39 | 'third-lang', 40 | 'de', 41 | 'third language, defaut german', 42 | short_name='t' 43 | ) 44 | 45 | flags.DEFINE_integer("width", 60, "display width", short_name='w') 46 | flags.DEFINE_boolean('copyto', True, 'copy thre result to clipboard') 47 | flags.DEFINE_boolean('copyfrom', True, 'copy from clipboard, default true (input taken fomr the terminal if false)') 48 | flags.DEFINE_boolean('debug', False, 'print debug messages.') 49 | flags.DEFINE_boolean('version', False, 'print version and exit') 50 | 51 | # FLAGS(shlex.split("app --from-lang=en")) 52 | 53 | 54 | # def main(argv): 55 | def proc_argv(argv): 56 | """ __main__ main """ 57 | 58 | version = "0.0.2" 59 | if FLAGS.version: 60 | print("deepl-tr-async %s" % version) 61 | 62 | if FLAGS.copyfrom: 63 | text = pyperclip.paste() 64 | logger.debug("text from clipboard: %s", text) 65 | else: 66 | text = ' '.join(argv[1:]) 67 | logger.debug("argv from terminal: %s", text) 68 | 69 | try: 70 | text = text.strip() 71 | except Exception as exc: 72 | logger.warning("text.strip() exc: %s, exiting...", exc) 73 | text = "" 74 | 75 | if not text: 76 | return None 77 | 78 | # del argv 79 | 80 | if FLAGS.debug: 81 | logzero.loglevel(10) # logging.DEBUG 82 | else: 83 | logzero.loglevel(20) # logging.INFO 84 | 85 | logger.debug('\n\t args: %s', dict((elm, getattr(FLAGS, elm)) for elm in FLAGS)) 86 | 87 | # to_lang = FLAGS.to_lang 88 | # from_lang = FLAGS.from_lang 89 | 90 | # to_lang = getattr(FLAGS, "to-lang") 91 | # from_lang = getattr(FLAGS, "from-lang") 92 | # width = getattr(FLAGS, "width") 93 | # copyto = getattr(FLAGS, "copyto") 94 | # debug = getattr(FLAGS, "debug") 95 | 96 | args = ['lang0', 'lang1', 'lang2', 'width' 97 | 'copyfrom', 'copyto', 'debug', ] 98 | # for elm in args: locals()[elm] = getattr(FLAGS, elm) 99 | lang0 = getattr(FLAGS, "mother-lang") 100 | lang1 = getattr(FLAGS, "second-lang") 101 | lang2 = getattr(FLAGS, "third-lang") 102 | width = FLAGS.width 103 | # copyfrom = FLAGS.copyfrom 104 | copyto = FLAGS.copyto 105 | debug = FLAGS.debug 106 | 107 | # if getattr(FLAGS, "debug"): 108 | if debug: 109 | logger.debug("args: %s", [[elm, getattr(FLAGS, elm)] for elm in args]) 110 | 111 | # make it unique and not the same as s_lang 112 | s_lang = detect_lang(text) 113 | logger.info(" detected language: %s", s_lang) 114 | 115 | lang_list = [] 116 | for elm in [lang0, lang1, lang2]: 117 | if elm not in lang_list and elm not in [s_lang]: 118 | lang_list.append(elm) 119 | 120 | if not lang_list: 121 | logger.info(" languages picked: %s", [lang0, lang1, lang2]) 122 | logger.warning(" Nothing to do. Select proper languages and source text and try again, exiting... ...") 123 | return None 124 | 125 | if len(lang_list) < 2: 126 | logger.warning(" Only one language %s is selected. We'll proceed tho.", lang_list) 127 | 128 | tasks = [] 129 | for elm in lang_list: 130 | task = deepl_tr_async( 131 | text, 132 | from_lang=s_lang, 133 | to_lang=elm 134 | ) 135 | tasks.append(task) 136 | 137 | # google tr 138 | tasks_g = [] 139 | for elm in lang_list: 140 | task = google_tr_async( 141 | text, 142 | from_lang=s_lang, 143 | to_lang=elm 144 | ) 145 | tasks_g.append(task) 146 | 147 | len_ = len(tasks) 148 | try: 149 | # trtext = LOOP.run_until_complete(task) 150 | _ = asyncio.gather(*tasks, *tasks_g) 151 | trtext_ = LOOP.run_until_complete(_) 152 | except Exception as exc: 153 | logger.error("LOOP.run_until_complete exc: %s", exc) 154 | trtext_ = [str(exc)] * len_ 155 | 156 | trtext, trtext_g = trtext_[:len_], trtext_[len_:] 157 | 158 | prefix = " deepl: " 159 | indent = ' ' * len(prefix) 160 | ftext = prefix 161 | for elm in trtext: 162 | if detect_lang(elm) in ['zh', 'ja']: 163 | ftext += fill(elm, width // 2, subsequent_indent=indent) + "\n" 164 | else: 165 | ftext += fill(elm, width, initial_indent=indent, subsequent_indent=indent) + "\n" 166 | 167 | prefix = " google: " 168 | indent = ' ' * len(prefix) 169 | ftext_g = prefix 170 | for elm in trtext_g: 171 | if detect_lang(elm) in ['zh', 'ja']: 172 | ftext_g += fill(elm, width // 2, subsequent_indent=indent) + "\n" 173 | else: 174 | ftext_g += fill(elm, width, initial_indent=indent, subsequent_indent=indent) + "\n" 175 | 176 | prefix = " deepl: " 177 | indent = ' ' * len(prefix) 178 | if detect_lang(text) in ['zh', 'ja']: 179 | text_ = fill(text, width // 2, initial_indent=indent, subsequent_indent=indent) + "\n" 180 | else: 181 | text_ = fill(text, width, initial_indent=indent, subsequent_indent=indent) + "\n" 182 | 183 | _ = text_ + ftext + ftext_g 184 | 185 | if copyto: 186 | pyperclip.copy(_) 187 | 188 | logger.info("translated to %s: \n\t%s", ', '.join(lang_list), _) 189 | 190 | 191 | def main(): # noqa: F811 192 | app.run(proc_argv) 193 | 194 | 195 | if __name__ == "__main__": 196 | # app.run(main) 197 | main() 198 | -------------------------------------------------------------------------------- /deepl_tr_async/deepl_langpair.py: -------------------------------------------------------------------------------- 1 | ''' 2 | deepl.com fanyi code 3 | 4 | based on you dao_langpair.py 5 | ''' 6 | import logging 7 | import pytest 8 | from fuzzywuzzy import fuzz 9 | from fuzzywuzzy import process 10 | import warnings 11 | 12 | warnings.filterwarnings("ignore", ".*pure-python.*") # regex 13 | 14 | LOGGER = logging.getLogger(__name__) 15 | LOGGER.addHandler(logging.NullHandler()) 16 | OTHER_CODES = ['zh_cn', 'ja', 'kr', 'ru', 'vi', 'zh', ] 17 | DEEPLTR_CODES = ['auto', 'en', 'de', 'fr', 'it', 'pl', 'es', 'nl'] 18 | 19 | OTHER_CODES = ['kr', 'vi', ] 20 | DEEPLTR_CODES = ['auto', 'zh', 'ja', 'ru', 'en', 'de', 21 | 'fr', 'it', 'pl', 'es', 'nl', 'pt'] 22 | 23 | 24 | class InvalidPair(Exception): 25 | '''in valid pair if 26 | src_lang not in DEEPLTR_CODES or src_lang not in DEEPLTR_CODES 27 | (not (src_lang not in DEEPLTR_CODES and src_lang in DEEPLTR_CODES)) 28 | ''' 29 | pass 30 | 31 | 32 | def deepl_langpair(srclang, tgtlang): 33 | ''' 34 | convert srclang, tgtlang to a pair suitable for deepl fanyi 35 | ''' 36 | # LOGGER.debug(" inp: %s, %s", srclang, tgtlang) 37 | 38 | try: 39 | srclang = srclang.lower().strip() 40 | except Exception as exc: 41 | LOGGER.warning(exc) 42 | srclang = '' 43 | 44 | # LOGGER.debug(" inp: %s, %s", srclang, tgtlang) 45 | 46 | try: 47 | tgtlang = tgtlang.lower().strip() 48 | except Exception as exc: 49 | LOGGER.warning(exc) 50 | tgtlang = '' 51 | 52 | if srclang in ['chinese', 'zhong', 'cn']: 53 | srclang = 'zh' 54 | if tgtlang in ['chinese', 'zhong', 'cn']: 55 | tgtlang = 'zh' 56 | 57 | if srclang == '': 58 | srclang = 'auto' 59 | if tgtlang == '': 60 | tgtlang = 'auto' 61 | 62 | # LOGGER.debug(" inp0: %s, %s", srclang, tgtlang) 63 | 64 | if srclang == 'auto' and tgtlang == 'auto': 65 | tgtlang = 'en' 66 | 67 | # LOGGER.debug(" inp1: %s, %s", srclang, tgtlang) 68 | 69 | if srclang != 'auto' and srclang != 'en' and tgtlang == 'auto': 70 | tgtlang = 'en' 71 | 72 | if srclang == 'en' and tgtlang == 'auto': 73 | tgtlang = 'de' 74 | 75 | if srclang in ['eng', 'english', 'en-US', ]: 76 | srclang = 'en' 77 | if tgtlang in ['eng', 'english', 'en-US', ]: 78 | tgtlang = 'en' 79 | 80 | # LOGGER.debug('out: %s, %s', srclang, tgtlang) 81 | 82 | if srclang not in DEEPLTR_CODES: 83 | src_score = process.extractOne(srclang, DEEPLTR_CODES + OTHER_CODES, scorer=fuzz.UWRatio) 84 | # srclang0 = srclang 85 | srclang = src_score[0] 86 | 87 | # LOGGER.warning(" %s not recognized, guessing to be %s ", srclang0, srclang) 88 | 89 | if tgtlang not in DEEPLTR_CODES: 90 | tgt_score = process.extractOne(tgtlang, DEEPLTR_CODES + OTHER_CODES, scorer=fuzz.UWRatio) 91 | # tgtlang0 = tgtlang 92 | tgtlang = tgt_score[0] 93 | # LOGGER.warning(" %s not recognized, guessing to be %s ", tgtlang0, tgtlang) 94 | 95 | if (srclang not in DEEPLTR_CODES) or (tgtlang not in DEEPLTR_CODES): 96 | msg = 'The pair {} is not valid.' 97 | msg = msg.format((srclang, tgtlang)) 98 | raise InvalidPair(msg) 99 | 100 | return srclang, tgtlang 101 | 102 | 103 | @pytest.mark.parametrize( 104 | "inpair, outpair", [ 105 | (('auto', 'auto'), ('auto', 'en')), 106 | (('en', 'auto'), ('en', 'de')), 107 | (('de', 'auto'), ('de', 'en')), 108 | (('', 'auto'), ('auto', 'en')), 109 | (('', ''), ('auto', 'en')), 110 | (('en', ''), ('en', 'de')), 111 | ((1, ''), ('auto', 'en')), # srclang strip() exception ==> '' >> 'auto' 112 | (('', 1), ('auto', 'en')), # tgtlan strip() exception ==> '' >> 'auto' 113 | # (('en', 'cn'), ('en', 'zh_ cn')), 114 | # (('chinese', 'en'), ('zh_ cn', 'en')), 115 | # process.extractOne("enn", DEEPLTR_CODES, scorer=fuzz.UWRatio) 116 | # (('chinese', 'enn'), ('zh_ cn', 'en')), 117 | (('enn', 'de'), ('en', 'de')), 118 | (('enn', 'De'), ('en', 'de')), 119 | (('eng', 'De'), ('en', 'de')), 120 | (('de', 'english'), ('de', 'en')), 121 | ] 122 | ) 123 | def test_pairs(inpair, outpair, caplog): 124 | '''test_pairs''' 125 | caplog.set_level(logging.DEBUG) 126 | msg = 'inp: {}, {} != {}' 127 | out = deepl_langpair(*inpair) 128 | msg = msg.format(inpair, out, outpair) 129 | assert out == outpair, msg 130 | 131 | 132 | @pytest.mark.parametrize( 133 | "inpair, outpair", [ 134 | (('en', 'cn'), ('en', 'zh')), 135 | (('chinese', 'en'), ('zh', 'en')), 136 | (('chinese', 'enn'), ('zh', 'en')), 137 | (('eng', 'zh'), ('en', 'zh')), 138 | (('zh-cn', 'english'), ('zh', 'en')), 139 | ] 140 | ) 141 | def test_pairs_zh(inpair, outpair, caplog): 142 | '''test_pairs''' 143 | caplog.set_level(logging.DEBUG) 144 | msg = 'inp: {}, {} != {}' 145 | out = deepl_langpair(*inpair) 146 | msg = msg.format(inpair, out, outpair) 147 | assert out == outpair, msg 148 | 149 | 150 | @pytest.mark.xfail(raises=InvalidPair) 151 | def test_invalid_enko(): 152 | '''invalid pair en ko''' 153 | srclang = 'en' 154 | tgtlang = 'ko' 155 | deepl_langpair(srclang, tgtlang) 156 | 157 | 158 | @pytest.mark.xfail(raises=InvalidPair) 159 | def test_invalid_zhen(): 160 | '''invalid pair zh en''' 161 | srclang = 'zh' 162 | tgtlang = 'en' 163 | deepl_langpair(srclang, tgtlang) 164 | -------------------------------------------------------------------------------- /deepl_tr_async/deepl_tr_async.py: -------------------------------------------------------------------------------- 1 | """ 2 | deepl via pyppeteer 3 | 4 | proxy = '103.28.206.65:888' 5 | browser = await launch( 6 | headless=False, 7 | args=['--proxy-server={}'.format(proxy), ] 8 | ) 9 | 10 | await page.goto('http://www.chenxm.cc/', {'timeout': 10000*20}) 11 | 12 | pip3.6 uninstall websockets # 卸载websockets 13 | pip3.6 install websockets==6.0 # 指定安装6.0版本``` 14 | 15 | https://hacpai.com/article/1566221786951 16 | '--window-size=1440x900' 17 | autoClose(bool):脚本完成时自动关闭浏览器进程。默认为 True 18 | https://zhuanlan.zhihu.com/p/97424787 19 | ["--disable-infobars", 20 | "--user-agent=Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/71.0.3578.98 Safari/537.36",] 21 | 22 | await page.setViewport({'width': 1366, 'height': 768}) 23 | 24 | https://github.com/miyakogi/pyppeteer/pull/160/files 25 | pyppeteer/connection.py line:44 -+ 26 | self._url, max_size=None, loop=self._loop) 27 | self._url, max_size=None, loop=self._loop, ping_interval=None, ping_timeout=None) 28 | pip install websockets==6.0 -U 29 | poetry add websockets==6.0 30 | 31 | # https://www.jianshu.com/p/611ed6b75d47 32 | await page.setViewport(viewport={'width':1280, 'height':800}) 33 | await page.setJavaScriptEnabled(enabled=True) 34 | await page.xpath('//div[@class="title-box"]/a') 35 | 36 | while not await page.querySelector('.t'): 37 | pass 38 | await page.screenshot({'path': 'example.png'}) 39 | 40 | $变为querySelector 41 | # Pyppeteer使用Python风格的函数名 42 | Page.querySelector()/Page.querySelectorAll()/Page.xpath() 43 | # 简写方式为: 44 | Page.J(), Page.JJ(), and Page.Jx() 45 | 46 | googlecn: 47 | de-zh zh-CN 48 | f"https://translate.google.cn/#view=home&op=translate&sl=de&tl=de&text={text}" 49 | 50 | en-zh zh-CN 51 | https://translate.google.cn/#view=home&op=translate&sl=zh-CN&tl=de&text={text}" 52 | 53 | .result-shield-container 54 | pq(content)(".result-shield-container").text() 55 | 56 | async fetch(url, css, browser=None): 57 | 58 | sys pyppeteer 0.0.17 websockets 59 | poetry pyppeteer 0.0.25 websockets 6.0 60 | """ 61 | # pylint: disable=too-many-arguments, too-many-locals 62 | # pylint: disable=too-many-statements, too-many-branches 63 | # pylint: disable=unused-import 64 | 65 | from typing import Any, List, Optional, Tuple, Union 66 | 67 | # import os 68 | # from pathlib import Path 69 | import asyncio 70 | from timeit import default_timer 71 | from urllib.parse import quote 72 | 73 | # from textwrap import wrap 74 | # from pyperclip import copy 75 | 76 | from pyppeteer import launch 77 | from pyquery import PyQuery as pq 78 | 79 | # import langid 80 | from polyglot.detect import Detector 81 | import logzero 82 | from logzero import logger 83 | # import dotenv 84 | # from environs import Env 85 | 86 | from deepl_tr_async.load_env import load_env 87 | from get_ppbrowser import BROWSER, LOOP, get_ppbrowser 88 | from get_ppbrowser.config import Settings 89 | 90 | config = Settings() 91 | HEADFUL = config.headful 92 | DEBUG = config.debug 93 | PROXY = config.proxy 94 | 95 | URL = r"https://www.deepl.com/translator" 96 | # LOOP = asyncio.get_event_loop() 97 | 98 | # dotenv.load_dotenv(verbose=1) 99 | # in shell or in .env 100 | # set HEADFUL=anything (include 0 False) to show browser 101 | 102 | _ = r""" 103 | ENV = Env() 104 | logger.info(" dotenv.find_dotenv(Path().cwd() / \".env\"): %s", Path().cwd() / '.env') 105 | _ = dotenv.find_dotenv(Path().cwd() / ".env") 106 | if _: 107 | logger.info(" Loading .env [%s]...", _) 108 | ENV.read_env(_, override=1) 109 | 110 | try: 111 | HEADFUL = ENV.bool("HEADFUL") 112 | except Exception as exc: 113 | logger.warning(' env.bool("HEADFUL") exc: %s', exc) 114 | HEADFUL = False 115 | try: 116 | DEBUG = ENV.bool("DEBUG") 117 | except Exception as exc: 118 | logger.warning( 119 | ' env.bool("DEBUG") [%s] exc: %s, DEBUG setting to False', 120 | os.getenv("DEBUG"), 121 | exc, 122 | ) 123 | DEBUG = False 124 | try: 125 | PROXY = ENV.str("PROXY") 126 | except Exception as exc: 127 | logger.warning(' env.str("PROXY") exc: %s', exc) 128 | PROXY = "" 129 | # """ 130 | 131 | _ = r''' # poetry add get-ppbrowser/from get_ppbrowser import BROWSER, get_ppbrowser 132 | 133 | try: 134 | HEADFUL = bool(load_env("headful", "bool")) 135 | except Exception as exc: 136 | logger.info("exc: %s", exc) 137 | HEADFUL = False 138 | try: 139 | DEBUG = bool(load_env("DEBUG", "bool")) 140 | except Exception as exc: 141 | logger.info("exc: %s", exc) 142 | DEBUG = False 143 | try: 144 | PROXY = str(load_env("PROXY", "str")) 145 | except Exception as exc: 146 | logger.info("exc: %s", exc) 147 | PROXY = "" 148 | 149 | logger.info(" HEADFUL: %s", HEADFUL) 150 | logger.info(" DEBUG: %s", DEBUG) 151 | logger.info(" PROXY: %s", PROXY) 152 | 153 | 154 | async def get_ppbrowser(headless=not HEADFUL, proxy: str = PROXY): 155 | """ get a puppeeter browser. 156 | 157 | headless=not HEADFUL; proxy: str = PROXY 158 | """ 159 | try: 160 | browser = await launch( 161 | args=[ 162 | "--disable-infobars", 163 | "--user-agent=Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/71.0.3578.98 Safari/537.36", 164 | "--window-size=1440x900", 165 | # "--autoClose=False", 166 | # f"--proxy-server={PROXY}", 167 | f"--proxy-server={proxy}", 168 | "--disable-popup-blocking", # 169 | ], 170 | executablePath=r"C:\Program Files (x86)\Google\Chrome\Application\chrome.exe", # use chrome 171 | # autoClose=False, 172 | headless=headless, 173 | dumpio=True, 174 | userDataDir="", 175 | ) 176 | except Exception as exc: 177 | logger.error("get_ppbrowser exc: %s", exc) 178 | raise 179 | # page = await browser.newPage() 180 | # await page.goto(url) 181 | # logger.debug("page.goto deepl time: %.2f s", default_timer() - then) 182 | return browser 183 | 184 | 185 | try: 186 | BROWSER = LOOP.run_until_complete(get_ppbrowser(not HEADFUL)) 187 | except Exception as exc: 188 | logger.error(" Unable to pyppeteer.launch exc: %s", exc) 189 | logger.info( 190 | "\n\t%s", 191 | r"Possible cause: abnormal exit from a previous session. Try `taskkill /f /im chrome.exe`", 192 | ) 193 | logger.warning(" %s", "Note that this will also kill your chrome browser.") 194 | raise SystemExit(1) 195 | # ''' 196 | 197 | # fmt: off 198 | # browser = LOOP.run_until_complete(get_ppbrowser(not HEADFUL)) 199 | async def deepl_tr_async( 200 | text: str, 201 | from_lang: str = "auto", 202 | to_lang: str = "auto", 203 | # headless: bool = not HEADFUL, 204 | debug: bool = False, 205 | # proxy: Optional[str] = None, 206 | waitfor: Optional[float] = None, 207 | browser=BROWSER, 208 | ) -> Optional[str]: 209 | """ deepl via pyppeteer 210 | from_lang = 'de' 211 | to_lang = 'en' 212 | debug = 1 213 | """ 214 | 215 | # fmt: on 216 | 217 | if debug or DEBUG: 218 | logzero.loglevel(10) 219 | else: 220 | logzero.loglevel(20) 221 | if from_lang.lower() == "auto": 222 | try: 223 | # from_lang = langid.classify(text)[0] 224 | from_lang = Detector(text).languages[0].code 225 | except Exception as exc: 226 | # logger.error("langid.classify failed: %s, setting from_lang to en", exc) 227 | logger.error("polyglot.detect.Detector failed: %s, setting from_lang to en", exc) 228 | from_lang = "en" 229 | if to_lang == "auto": 230 | if from_lang not in ["en"]: 231 | to_lang = "en" 232 | else: 233 | to_lang = "de" 234 | 235 | langs = ["auto", "en", "de", "zh", "fr", "es", "pt", "it", "nl", "pl", "ru", "ja"] 236 | 237 | try: 238 | from_lang = from_lang.lower() 239 | except Exception as exc: 240 | from_lang = "en" 241 | logger.warning("%s", exc) 242 | try: 243 | to_lang = to_lang.lower() 244 | except Exception as exc: 245 | to_lang = "en" 246 | logger.warning("%s", exc) 247 | 248 | if from_lang not in langs: 249 | logger.warning(" from_lang [%s] not in the langs set, setting to en", from_lang) 250 | from_lang = "en" 251 | if to_lang not in langs: 252 | logger.warning(" to_lang [%s] not in the langs set, setting to en", to_lang) 253 | to_lang = "en" 254 | 255 | if from_lang == to_lang: 256 | logger.warning( 257 | " from_lang [%s] and to_lang [%s] are idnetical, nothing to do", 258 | from_lang, 259 | to_lang, 260 | ) 261 | return text 262 | 263 | then = default_timer() 264 | count = 0 265 | while count < 3: 266 | count += 1 267 | try: 268 | page = await browser.newPage() 269 | break 270 | except Exception as exc: 271 | logger.error(" browser.newPage exc: %s, failed attempt: %s", exc, count) 272 | await asyncio.sleep(0) 273 | else: 274 | # giving up 275 | return 276 | 277 | # set timeout to 45 s, default 30 s 278 | if HEADFUL: 279 | page.setDefaultNavigationTimeout(0) 280 | else: 281 | page.setDefaultNavigationTimeout(75000) 282 | 283 | url_ = f"{URL}#{from_lang}/{to_lang}/{quote(text)}" 284 | # url_ = f'{URL}#{from_lang}/{to_lang}/' 285 | 286 | # await page.type(".lmt__source_textarea", text + text + ' ' * 90) 287 | 288 | count = 0 289 | while count < 3: 290 | count += 1 291 | try: 292 | # await page.goto(url_) 293 | await page.goto(url_, {"timeout": 90 * 1000}) 294 | # await page.goto(url_, {"timeout": 0}) 295 | break 296 | except Exception as exc: 297 | await asyncio.sleep(0) 298 | page = await browser.newPage() 299 | logger.warning("page.goto exc: %s, attempt %s", str(exc)[:100], count) 300 | else: 301 | # return 302 | raise Exception("Unable to fetch %s..." % url_[:20]) 303 | 304 | # wait for input area ".lmt__source_textarea" 305 | try: 306 | # await page.waitFor(".lmt__message_box2__content") 307 | await page.waitForSelector(".lmt__source_textarea", {"timeout": 1000}) # ms 308 | logger.debug(" *** .lmt__source_textarea success") 309 | # except TimeoutError: 310 | except Exception as exc: 311 | if debug: 312 | logger.error("Timedout: %s, waiting for 500 ms more", exc) 313 | logger.error("text: %s", text) 314 | await asyncio.sleep(0.5) 315 | # raise 316 | 317 | logger.debug("page.goto(url_) time: %.2f s", default_timer() - then) 318 | then = default_timer() 319 | 320 | # .lmt__message_box2__content 321 | 322 | # await page.waitFor(2500) # ms 323 | 324 | # wait for popup to be visible 325 | _ = """ 326 | try: 327 | # await page.waitFor(".lmt__message_box2__content") 328 | await page.waitForSelector(".lmt__message_box2__content", {"timeout": 1000}) # ms 329 | # except TimeoutError: 330 | except Exception as exc: 331 | if debug: 332 | logger.error("Timedout: %s, waiting for 500 ms more", exc) 333 | await asyncio.sleep(0.5) 334 | # raise 335 | """ 336 | 337 | # _ = int(min(10, len(text) * 0.2)) 338 | # await page.type(".lmt__source_textarea", text + ' ' * _) 339 | 340 | if waitfor is None: 341 | _ = max(100, len(text) * 3.6) 342 | logger.debug("waiting for %.1f ms", _) 343 | else: 344 | try: 345 | _ = float(waitfor) 346 | except Exception as exc: 347 | logger.warning( 348 | " invalif waitfor [%s]: %s, setting to auto-adjust", waitfor, exc 349 | ) 350 | _ = max(100, len(text) * 3.6) 351 | 352 | logger.debug("preset fixed waiting for %.1f ms", _) 353 | 354 | # ".lmt__translations_as_text" 355 | # await page.waitFor(".lmt__translations_as_text", {"timeout": _}) # ms 356 | 357 | # logger.debug(" is page closed? ") 358 | try: 359 | await page.waitFor(_) 360 | except Exception as exc: 361 | logger.warning(" page.waitFor exc: %s", exc) 362 | try: 363 | content = await page.content() 364 | except Exception as exc: 365 | logger.warning(" page.waitFor exc: %s", exc) 366 | content = '
%s
' % exc 367 | 368 | doc = pq(content) 369 | res = doc(".lmt__translations_as_text").text() 370 | 371 | count = -1 372 | while count < 50: 373 | count += 1 374 | logger.debug(" extra %s x 100 ms", count + 1) 375 | await page.waitFor(100) 376 | 377 | content = await page.content() 378 | doc = pq(content) 379 | res = doc(".lmt__translations_as_text").text() 380 | if res: 381 | break 382 | await asyncio.sleep(0) 383 | 384 | logger.debug("time: %.2f s", default_timer() - then) 385 | 386 | logger.debug("res: %s", res) 387 | 388 | if not debug: 389 | pass 390 | await page.close() 391 | 392 | await asyncio.sleep(0.2) 393 | 394 | # copy('\n'.join(wrap(res, 45))) 395 | 396 | # logger.info('exit: %s', text[:200]) 397 | 398 | return res 399 | 400 | 401 | def deepl_mpages( # pragrma: no cover 402 | sents: Union[str, List[str]], 403 | from_lang: str = "auto", 404 | to_lang: str = "auto", 405 | # headless: bool = not HEADFUL, 406 | debug: bool = False, 407 | waitfor: Optional[float] = None, 408 | loop=None, 409 | browser=BROWSER, 410 | ) -> Union[Tuple[Optional[str]], Any]: 411 | # ) -> List[Union[Optional[str], Any]]: 412 | """ multiple pages 413 | """ 414 | 415 | if loop is None: 416 | loop = LOOP 417 | if loop.is_closed(): 418 | loop = asyncio.new_event_loop() 419 | 420 | if isinstance(sents, str): 421 | sents = [sents] 422 | 423 | # browser = await get_ppbrowser(headless) 424 | tasks = ( 425 | deepl_tr_async( 426 | elm, 427 | from_lang=from_lang, 428 | to_lang=to_lang, 429 | debug=debug, 430 | waitfor=waitfor, 431 | browser=browser, 432 | ) 433 | for elm in sents 434 | ) 435 | 436 | try: 437 | res = loop.run_until_complete(asyncio.gather(*tasks)) 438 | except Exception as exc: 439 | logger.error(" loop.run_until_complete exc: %s", exc) 440 | res = str(exc) 441 | 442 | if not debug: 443 | pass 444 | # LOOP.run_until_complete(browser.close()) 445 | 446 | return res 447 | 448 | 449 | def main(): 450 | """Main.""" 451 | # from time import sleep 452 | 453 | # pylint: disable=line-too-long 454 | text = """Die Rohstoffpreise sind abgestürzt, die Coronakrise trifft die Exportländer mit Wucht. Das Öl wird weiter gefördert, aber derzeit nicht gebraucht. Nun könnten einige Staaten in eine gefährliche Kreditklemme geraten.""" 455 | 456 | text1 = """Im Westen Kanadas reißt die Ölindustrie tiefe Narben in die Landschaft. Dort bauen die Unternehmen Ölsande ab: Zwei Tonnen Material sind nötig, um eine Tonne Öl zu gewinnen. Es ist die wohl aufwendigste und dreckigste Art der Brennstoff-Produktion. Und gegenwärtig auch die unwirtschaftlichste.""" 457 | 458 | # text2 = f"{text} {text1} " # pylint: disable=unused-variable 459 | 460 | debug = DEBUG 461 | 462 | # _ = """ 463 | then = default_timer() 464 | 465 | # browser = LOOP.run_until_complete(get_ppbrowser()) 466 | # logger.info(" get_ppbrowser time: %.2f", default_timer() - then) 467 | 468 | # res = LOOP.run_until_complete(deepl_(text1, debug=debug)) 469 | # res = LOOP.run_until_complete(deepl_("verramschen", debug=debug)) 470 | res = deepl_mpages(["verramschen"], debug=debug) 471 | 472 | logger.info(" deepl_mpage 2a= %s", res) 473 | logger.info(" deepl_mpage 2a=== time: %.2f", default_timer() - then) 474 | # """ 475 | 476 | # sleep(12) 477 | 478 | _ = """ 479 | then = default_timer() 480 | coros = [ 481 | # deepl_(text, debug=debug), 482 | deepl_(text, debug=debug), 483 | deepl_(text1, debug=debug), 484 | # deepl_(text1, to_lang="", debug=debug), 485 | # deepl_("verramschen", debug=debug), 486 | # deepl_("verramschen", to_lang="zh", debug=debug), 487 | # deepl_("sell off", to_lang="zh", debug=debug), 488 | # deepl_("sell off", to_lang="fr", debug=debug), 489 | ] 490 | res12 = LOOP.run_until_complete(asyncio.gather(*coros)) 491 | logger.info(" 3== %s", res12) 492 | logger.info(' 3==== time: %.2f', default_timer() - then) 493 | # """ 494 | 495 | then = default_timer() 496 | # browser = LOOP.run_until_complete(get_ppbrowser()) 497 | # logger.info(" -- get_ppbrowser time: %.2f", default_timer() - then) 498 | 499 | res12 = deepl_mpages( 500 | ["verramschen", "verramschen", text, text1, text1, text1], debug=debug 501 | ) 502 | logger.info(" deepl_mpage 3== %s", res12) 503 | logger.info(" deepl_mpage 3==== time: %.2f", default_timer() - then) 504 | 505 | 506 | # pylint: disable=invalid-name 507 | if __name__ == "__main__": 508 | main() 509 | -------------------------------------------------------------------------------- /deepl_tr_async/detect_lang.py: -------------------------------------------------------------------------------- 1 | """ 2 | 3 | in mypython 4 | Detect language using longid.classify. 5 | 6 | detct as Chinese if chinese_char_ratio>= threthold else dectect_language() 7 | 8 | """ 9 | 10 | # from detect_language import detect_language 11 | # from langdetect import detect 12 | # import langid 13 | # from chinese_char_ratio import chinese_char_ratio 14 | 15 | from typing import Union 16 | from polyglot.detect import Detector 17 | 18 | from logzero import logger as LOGGER 19 | 20 | 21 | # def detect_lang(text1, threthold=0.1, checklen=3000): 22 | # def detect_lang(text1, checklen=3000, langs=None): 23 | # def detect_lang(text1, checklen=3000): 24 | def detect_lang(text1: str, name: Union[bool, int] = False) -> str: 25 | """ 26 | return name.lower() if name is True 27 | 28 | Detect Chinese and other languages using polyglot. 29 | """ 30 | 31 | if not text1.strip(): 32 | detected = "en" 33 | if name: 34 | detected = "english" 35 | else: 36 | try: 37 | # detected = Detector(text1).languages[0].code 38 | _ = Detector(text1).language 39 | if name: 40 | detected = _.name.lower() 41 | else: 42 | detected = _.code 43 | except Exception as exc: 44 | # LOGGER.debug(" langid.classify failed: %s", exc) 45 | LOGGER.debug( 46 | " Detector(text1).language[0] failed: %s, setting to 'en'/'english' ", 47 | exc, 48 | ) 49 | if name: 50 | detected = "english" 51 | else: 52 | detected = "en" 53 | 54 | return detected 55 | -------------------------------------------------------------------------------- /deepl_tr_async/google_langpair.py: -------------------------------------------------------------------------------- 1 | ''' 2 | google fanyi code 3 | 4 | https://cloud.google.com/translate/docs/languages 5 | ''' 6 | import logging 7 | import pytest 8 | from fuzzywuzzy import fuzz 9 | from fuzzywuzzy import process 10 | import warnings 11 | 12 | warnings.filterwarnings("ignore", ".*pure-python.*") # regex 13 | 14 | LOGGER = logging.getLogger(__name__) 15 | LOGGER.addHandler(logging.NullHandler()) 16 | 17 | # BD_CODES = ['auto', 'zh', 'en', 'yue', 'wyw', 'jp', 'kor', 'fra', 'spa', 'th', 'ara', 'ru', 'pt', 'de', 'it', 'el', 'nl', 'pl', 'bul', 'est', 'dan', 'fin', 'cs', 'rom', 'slo', 'swe', 'hu', 'cht', 'vie'] # NOQA # pylint: disable=C0301 18 | GOOGLETR_CODES = ['auto', 'af', 'sq', 'am', 'ar', 'hy', 'az', 'eu', 'be', 'bn', 'bs', 'bg', 'ca', 'ceb', 'zh-CN', 'zh-TW', 'hr', 'cs', 'da', 'nl', 'en', 'eo', 'et', 'fi', 'fr', 'fy', 'gl', 'ka', 'de', 'el', 'gu', 'ht', 'ha', 'haw', 'iw', 'hi', 'hmn', 'hu', 'is', 'ig', 'id', 'ga', 'it', 'ja', 'jw', 'kn', 'kk', 'km', 'ko', 'ku', 'ky', 'lo', 'la', 'lv', 'lt', 'lb', 'mk', 'mg', 'ms', 'ml', 'mt', 'mi', 'mr', 'mn', 'my', 'ne', 'no', 'ny', 'ps', 'fa', 'pl', 'pt', 'pa', 'ro', 'ru', 'sm', 'gd', 'sr', 'st', 'sn', 'sd', 'si', 'sk', 'sl', 'so', 'es', 'su', 'sw', 'sv', 'tl', 'tg', 'ta', 'te', 'th', 'tr', 'uk', 'ur', 'uz', 'vi', 'cy', 'xh', 'yi', 'yo', 'zu', ] 19 | 20 | 21 | def google_langpair(srclang, tgtlang): 22 | ''' 23 | convert srclang, tgtlang to a pair suitable for google fanyi 24 | ''' 25 | # LOGGER.debug(" inp: %s, %s", srclang, tgtlang) 26 | 27 | try: 28 | srclang = srclang.lower().strip() 29 | except Exception as exc: 30 | LOGGER.warning(exc) 31 | srclang = '' 32 | 33 | # LOGGER.debug(" inp: %s, %s", srclang, tgtlang) 34 | 35 | try: 36 | tgtlang = tgtlang.lower().strip() 37 | except Exception as exc: 38 | LOGGER.warning(exc) 39 | tgtlang = '' 40 | 41 | if srclang == '': 42 | srclang = 'auto' 43 | if tgtlang == '': 44 | tgtlang = 'auto' 45 | 46 | # LOGGER.debug(" inp0: %s, %s", srclang, tgtlang) 47 | 48 | if srclang == 'auto' and tgtlang == 'auto': 49 | tgtlang = 'zh-CN' 50 | 51 | # LOGGER.debug(" inp1: %s, %s", srclang, tgtlang) 52 | 53 | if srclang != 'auto' and tgtlang == 'auto': 54 | tgtlang = 'zh-CN' 55 | 56 | if srclang in ['cn', 'chinese', 'zhong', 'zhongwen']: 57 | srclang = 'zh-CN' 58 | if tgtlang in ['cn', 'chinese', 'zhong', 'zhongwen']: 59 | tgtlang = 'zh-CN' 60 | 61 | # LOGGER.debug('out: %s, %s', srclang, tgtlang) 62 | 63 | if srclang not in GOOGLETR_CODES: 64 | src_score = process.extractOne(srclang, GOOGLETR_CODES, scorer=fuzz.UWRatio) 65 | srclang0 = srclang 66 | srclang = src_score[0] 67 | 68 | # LOGGER.warning(" %s not recognized, guessing to be %s ", srclang0, srclang) 69 | 70 | if tgtlang not in GOOGLETR_CODES: 71 | tgt_score = process.extractOne(tgtlang, GOOGLETR_CODES, scorer=fuzz.UWRatio) 72 | tgtlang0 = tgtlang 73 | tgtlang = tgt_score[0] 74 | # LOGGER.warning(" %s not recognized, guessing to be %s ", tgtlang0, tgtlang) 75 | 76 | return srclang, tgtlang 77 | 78 | 79 | @pytest.mark.parametrize( 80 | "inpair, outpair", [ 81 | (('auto', 'auto'), ('auto', 'zh-CN')), 82 | (('en', 'auto'), ('en', 'zh-CN')), 83 | (('', ''), ('auto', 'zh-CN')), 84 | (('en', ''), ('en', 'zh-CN')), 85 | ((1, ''), ('auto', 'zh-CN')), # srclang strip() exception ==> '' >> 'auto' 86 | (('', 1), ('auto', 'zh-CN')), # tgtlan strip() exception ==> '' >> 'auto' 87 | (('en', 'cn'), ('en', 'zh-CN')), 88 | (('EN', 'cn'), ('en', 'zh-CN')), 89 | (('EN', 'Cn'), ('en', 'zh-CN')), 90 | (('chinese', 'en'), ('zh-CN', 'en')), 91 | (('chinese', 'En'), ('zh-CN', 'en')), 92 | # process.extractOne("enn", GOOGLETR_CODES, scorer=fuzz.UWRatio) 93 | (('chinese', 'enn'), ('zh-CN', 'en')), 94 | (('enn', 'chinese'), ('en', 'zh-CN')), 95 | (('enn', 'zh TW'), ('en', 'zh-TW')), 96 | ] 97 | ) 98 | def test_pairs(inpair, outpair, caplog): 99 | '''test_pairs''' 100 | caplog.set_level(logging.DEBUG) 101 | assert google_langpair(*inpair) == outpair 102 | -------------------------------------------------------------------------------- /deepl_tr_async/google_tr_async.py: -------------------------------------------------------------------------------- 1 | """ 2 | 3 | modified from deepl_tr_async 4 | 5 | googlecn: 6 | de-zh zh-CN 7 | f"https://translate.google.cn/#view=home&op=translate&sl=de&tl=de&text={text}" 8 | 9 | en-zh zh-CN 10 | https://translate.google.cn/#view=home&op=translate&sl=zh-CN&tl=de&text={text}" 11 | 12 | .result-shield-container 13 | pq(content)(".result-shield-container").text() 14 | 15 | async fetch(url, css, browser=None): 16 | 17 | """ 18 | # pylint: disable=too-many-arguments, too-many-locals 19 | # pylint: disable=too-many-statements, too-many-branches 20 | # pylint: disable=unused-import 21 | 22 | from typing import Any, List, Optional, Tuple, Union 23 | 24 | # import os 25 | import asyncio 26 | import re 27 | from timeit import default_timer 28 | from urllib.parse import quote 29 | 30 | from pyquery import PyQuery as pq 31 | # import dotenv 32 | 33 | from polyglot.detect import Detector 34 | import logzero 35 | from logzero import logger 36 | # from environs import Env 37 | 38 | # from deepl_tr_async import BROWSER, get_ppbrowser, HEADFUL, DEBUG 39 | from get_ppbrowser import BROWSER, get_ppbrowser 40 | from deepl_tr_async.google_langpair import google_langpair 41 | from get_ppbrowser.config import Settings 42 | 43 | config = Settings() 44 | HEADFUL = config.headful 45 | DEBUG = config.debug 46 | PROXY = config.proxy 47 | 48 | URL = r"https://translate.google.cn/#view=home&op=translate&" 49 | # if not LOOP.is_closed(): LOOP = asyncio.new_event_loop() 50 | 51 | LOOP = asyncio.get_event_loop() 52 | 53 | # dotenv.load_dotenv(verbose=1) 54 | # in shell or in .env 55 | # set HEADFUL=anything (include 0 False) to show browser 56 | 57 | _ = """\ 58 | ENV = Env() 59 | _ = dotenv.find_dotenv() 60 | if _: 61 | ENV.read_env(_, override=1) 62 | 63 | try: 64 | HEADFUL = ENV.bool("HEADFUL") 65 | except Exception as exc: 66 | logger.warning(' env.bool("HEADFUL") exc: %s', exc) 67 | HEADFUL = False 68 | try: 69 | DEBUG = ENV.bool("DEBUG") 70 | except Exception as exc: 71 | logger.warning( 72 | ' env.bool("DEBUG") [%s] exc: %s, DEBUG setting to False', 73 | os.getenv("DEBUG"), 74 | exc, 75 | ) 76 | DEBUG = False 77 | logger.info(" HEADFUL: %s", HEADFUL) 78 | logger.info(" DEBUG: %s", DEBUG) 79 | # """ 80 | 81 | 82 | # fmt: off 83 | async def google_tr_async( # noqa: C901 84 | text: str, 85 | from_lang: str = "auto", 86 | to_lang: str = "auto", 87 | # headless: bool = not HEADFUL, 88 | debug: bool = False, 89 | # proxy: Optional[str] = None, 90 | waitfor: Optional[float] = None, 91 | browser=BROWSER, 92 | ) -> Optional[str]: 93 | """ google via pyppeteer 94 | from_lang = 'de' 95 | to_lang = 'en' 96 | from_lang = 'en' 97 | to_lang = 'zh' 98 | debug = 1 99 | waitfor: Optional[float] = None 100 | browser=BROWSER 101 | """ 102 | 103 | # browser = await get_ppbrowser(headless) 104 | 105 | # fmt: on 106 | 107 | if debug: 108 | logzero.loglevel(10) 109 | else: 110 | logzero.loglevel(20) 111 | if from_lang.lower() == "auto": 112 | try: 113 | # from_lang = langid.classify(text)[0] 114 | from_lang = Detector(text).language.code 115 | except Exception as exc: 116 | # logger.error("langid.classify failed: %s, setting from_lang to en", exc) 117 | logger.error("polyglot.detect.Detector failed: %s, setting from_lang to en", exc) 118 | from_lang = "en" 119 | if to_lang == "auto": 120 | if from_lang not in ["en"]: 121 | to_lang = "en" 122 | else: 123 | to_lang = "de" 124 | 125 | # langs = ["auto", "en", "de", "zh", "fr", "es", "pt", "it", "nl", "pl", "ru", "ja"] 126 | 127 | try: 128 | from_lang = from_lang.lower() 129 | except Exception as exc: 130 | from_lang = "en" 131 | logger.warning("%s", exc) 132 | try: 133 | to_lang = to_lang.lower() 134 | except Exception as exc: 135 | to_lang = "en" 136 | logger.warning("%s", exc) 137 | 138 | _ = """ 139 | if from_lang not in langs: 140 | logger.warning(" from_lang [%s] not in the langs set, setting to en", from_lang) 141 | from_lang = "en" 142 | if to_lang not in langs: 143 | logger.warning(" to_lang [%s] not in the langs set, setting to en", to_lang) 144 | to_lang = "en" 145 | # """ 146 | 147 | logger.debug("langpair: %s, %s", from_lang, to_lang) 148 | from_lang, to_lang = google_langpair(from_lang, to_lang) 149 | logger.debug("converted langpair: %s, %s", from_lang, to_lang) 150 | 151 | if from_lang == to_lang: 152 | logger.warning( 153 | " from_lang [%s] and to_lang [%s] are idnetical, nothing to do", 154 | from_lang, 155 | to_lang, 156 | ) 157 | return text 158 | 159 | then = default_timer() 160 | count = 0 161 | while count < 3: 162 | count += 1 163 | try: 164 | page = await browser.newPage() 165 | break 166 | except Exception as exc: 167 | logger.error(" browser.newPage exc: %s, failed attempt: %s", exc, count) 168 | browser = await get_ppbrowser(not HEADFUL) 169 | await asyncio.sleep(0) 170 | else: 171 | # giving up 172 | logger.warning("Unable to make newPage work...") 173 | raise Exception("Unable to get newPage to work, giving up...") 174 | 175 | # set timeout, default is 30 s 176 | _ = """\ 177 | if HEADFUL: 178 | page.setDefaultNavigationTimeout(0) 179 | else: 180 | page.setDefaultNavigationTimeout(75000) 181 | # """ 182 | 183 | page.setDefaultNavigationTimeout(75000) 184 | 185 | # url_ = f'{URL}#{from_lang}/{to_lang}/' 186 | # url_ = f"{URL}#{from_lang}/{to_lang}/{quote(text)}" 187 | 188 | # sl=de&tl=de&text={text} 189 | url_ = f"{URL}sl={from_lang}&tl={to_lang}&text={quote(text)}" 190 | 191 | # await page.type(".lmt__source_textarea", text + text + ' ' * 90) 192 | 193 | count = 0 194 | while count < 3: 195 | count += 1 196 | try: 197 | # await page.goto(url_) 198 | # await page.goto(url_, {"timeout": 90 * 1000}) 199 | await page.goto(url_, {"timeout": 19 * 1000}) 200 | # await page.goto(url_, {"timeout": 0}) 201 | break 202 | except Exception as exc: 203 | await asyncio.sleep(0) 204 | page = await browser.newPage() 205 | logger.warning("page.goto exc: %s, attempt %s", str(exc)[:100], count) 206 | else: 207 | # return 208 | logger.error("Unable to fetch %s...", url_[:50]) 209 | raise Exception("Unable to fetch %s..." % url_[:50]) 210 | 211 | # wait for input area ".lmt__source_textarea" 212 | # wait for input area ".result-shield-container" 213 | try: 214 | # await page.waitFor(".lmt__message_box2__content") 215 | await page.waitForSelector(".result-shield-container", {"timeout": 1000}) # ms 216 | logger.debug(" *** .result-shield-container") 217 | # except TimeoutError: 218 | except Exception as exc: 219 | if debug: 220 | logger.error("Timedout: %s, waiting for 500 ms more", exc) 221 | logger.error("text: %s", text) 222 | await asyncio.sleep(0.5) 223 | # raise 224 | 225 | logger.debug("page.goto(url_) time: %.2f s", default_timer() - then) 226 | then = default_timer() 227 | 228 | # .lmt__message_box2__content 229 | 230 | # await page.waitFor(2500) # ms 231 | 232 | # wait for popup to be visible 233 | _ = """ 234 | try: 235 | # await page.waitFor(".lmt__message_box2__content") 236 | await page.waitForSelector(".lmt__message_box2__content", {"timeout": 1000}) # ms 237 | # except TimeoutError: 238 | except Exception as exc: 239 | if debug: 240 | logger.error("Timedout: %s, waiting for 500 ms more", exc) 241 | await asyncio.sleep(0.5) 242 | # raise 243 | """ 244 | 245 | # _ = int(min(10, len(text) * 0.2)) 246 | # await page.type(".lmt__source_textarea", text + ' ' * _) 247 | 248 | if waitfor is None: 249 | _ = max(100, len(text) * 3.6) 250 | logger.debug("waiting for %.1f ms", _) 251 | else: 252 | try: 253 | _ = float(waitfor) 254 | except Exception as exc: 255 | logger.warning( 256 | " invalif waitfor [%s]: %s, setting to auto-adjust", waitfor, exc 257 | ) 258 | _ = max(100, len(text) * 3.6) 259 | 260 | logger.debug("preset fixed waiting for %.1f ms", _) 261 | 262 | # ".lmt__translations_as_text" 263 | # await page.waitFor(".lmt__translations_as_text", {"timeout": _}) # ms 264 | 265 | # logger.debug(" is page closed? ") 266 | try: 267 | await page.waitFor(_) 268 | except Exception as exc: 269 | logger.warning(" page.waitFor exc: %s", exc) 270 | try: 271 | content = await page.content() 272 | except Exception as exc: 273 | logger.warning(" page.waitFor exc: %s", exc) 274 | content = '
%s
' % exc 275 | 276 | def filter_trtext(content0: str, to_lang0: str) -> str: 277 | doc = pq(content0) 278 | res: str = doc(".result-shield-container").text() 279 | 280 | # when .result-shield-container fails, 281 | # use regex 282 | if not res: 283 | _ = re.findall(rf'class=".{{4,7}}" lang="{to_lang0}">[^<].*? 287 | res = res.split(f'lang="{to_lang0}">')[1] 288 | 289 | # return '' when res is [] 290 | if not res: 291 | res = "" 292 | return res 293 | 294 | res = filter_trtext(content, to_lang) 295 | 296 | count = -1 297 | # wait a bit longer if res is empty 298 | while count < 50 and not res: 299 | count += 1 300 | logger.debug(" extra %s x 100 ms", count + 1) 301 | await page.waitFor(100) 302 | 303 | content = await page.content() 304 | _ = """ 305 | doc = pq(content) 306 | res = doc(".result-shield-container").text() 307 | # """ 308 | 309 | res = filter_trtext(content, to_lang) 310 | 311 | if res: 312 | break 313 | await asyncio.sleep(0) 314 | 315 | logger.debug("time: %.2f s", default_timer() - then) 316 | 317 | logger.debug("res: %s", res) 318 | 319 | if not debug: 320 | pass 321 | await page.close() 322 | 323 | await asyncio.sleep(0) 324 | 325 | # copy('\n'.join(wrap(res, 45))) 326 | 327 | # logger.info('exit: %s', text[:200]) 328 | 329 | return res 330 | 331 | 332 | def google_mpages( # pragrma: no cover 333 | sents: Union[str, List[str]], 334 | from_lang: str = "auto", 335 | to_lang: str = "auto", 336 | # headless: bool = not HEADFUL, 337 | debug: bool = False, 338 | waitfor: Optional[float] = None, 339 | loop=None, 340 | browser=BROWSER, 341 | ) -> Union[Tuple[Optional[str]], Any]: 342 | # ) -> List[Union[Optional[str], Any]]: 343 | """ multiple pages 344 | """ 345 | 346 | if loop is None: # pragrma: no cover 347 | loop = LOOP 348 | # if loop.is_closed(): loop = asyncio.new_event_loop() 349 | 350 | if isinstance(sents, str): 351 | sents = [sents] 352 | 353 | tasks = ( 354 | google_tr_async( 355 | elm, 356 | from_lang=from_lang, 357 | to_lang=to_lang, 358 | debug=debug, 359 | waitfor=waitfor, 360 | browser=browser, 361 | ) 362 | for elm in sents 363 | ) 364 | 365 | try: 366 | res = loop.run_until_complete(asyncio.gather(*tasks)) 367 | except Exception as exc: 368 | logger.error(" loop.run_until_complete exc: %s", exc) 369 | res = str(exc) 370 | 371 | if not debug: 372 | pass 373 | # LOOP.run_until_complete(browser.close()) 374 | 375 | return res 376 | 377 | 378 | def main(): 379 | """main""" 380 | # from time import sleep 381 | 382 | # pylint: disable=line-too-long 383 | text = """Die Rohstoffpreise sind abgestürzt, die Coronakrise trifft die Exportländer mit Wucht. Das Öl wird weiter gefördert, aber derzeit nicht gebraucht. Nun könnten einige Staaten in eine gefährliche Kreditklemme geraten.""" 384 | 385 | text1 = """Im Westen Kanadas reißt die Ölindustrie tiefe Narben in die Landschaft. Dort bauen die Unternehmen Ölsande ab: Zwei Tonnen Material sind nötig, um eine Tonne Öl zu gewinnen. Es ist die wohl aufwendigste und dreckigste Art der Brennstoff-Produktion. Und gegenwärtig auch die unwirtschaftlichste.""" 386 | 387 | # text2 = f"{text} {text1} " # pylint: disable=unused-variable 388 | 389 | debug = DEBUG 390 | 391 | # _ = """ 392 | then = default_timer() 393 | res = LOOP.run_until_complete(google_tr_async(text, debug=debug)) 394 | logger.info(" 1= %s", res) 395 | logger.info('1=== time: %.2f', default_timer() - then) 396 | # """ 397 | 398 | # sleep(5) 399 | 400 | # _ = """ 401 | then = default_timer() 402 | 403 | # br = LOOP.run_until_complete(get_ppbrowser(1)) 404 | res = LOOP.run_until_complete(google_tr_async(text1, debug=debug)) 405 | logger.info(" 2= %s", res) 406 | logger.info(' 2=== time: %.2f', default_timer() - then) 407 | # """ 408 | # sleep(12) 409 | 410 | # _ = """ 411 | then = default_timer() 412 | 413 | # browser = LOOP.run_until_complete(get_ppbrowser()) 414 | # logger.info(" get_ppbrowser time: %.2f", default_timer() - then) 415 | 416 | # res = LOOP.run_until_complete(google_(text1, debug=debug)) 417 | # res = LOOP.run_until_complete(google_("verramschen", debug=debug)) 418 | res = google_mpages(["verramschen"], debug=debug) 419 | 420 | logger.info(" google_mpage 2a= %s", res) 421 | logger.info(" google_mpage 2a=== time: %.2f", default_timer() - then) 422 | # """ 423 | 424 | # sleep(12) 425 | 426 | _ = """ 427 | then = default_timer() 428 | coros = [ 429 | # google_(text, debug=debug), 430 | google_(text, debug=debug), 431 | google_(text1, debug=debug), 432 | # google_(text1, to_lang="", debug=debug), 433 | # google_("verramschen", debug=debug), 434 | # google_("verramschen", to_lang="zh", debug=debug), 435 | # google_("sell off", to_lang="zh", debug=debug), 436 | # google_("sell off", to_lang="fr", debug=debug), 437 | ] 438 | res12 = LOOP.run_until_complete(asyncio.gather(*coros)) 439 | logger.info(" 3== %s", res12) 440 | logger.info(' 3==== time: %.2f', default_timer() - then) 441 | # """ 442 | 443 | then = default_timer() 444 | # browser = LOOP.run_until_complete(get_ppbrowser()) 445 | # logger.info(" -- get_ppbrowser time: %.2f", default_timer() - then) 446 | 447 | res12 = google_mpages( 448 | ["verramschen", "verramschen", text, text1, text1, text1], debug=debug 449 | ) 450 | logger.info(" google_mpage 3== %s", res12) 451 | logger.info(" google_mpage 3==== time: %.2f", default_timer() - then) 452 | 453 | 454 | # pylint: disable=invalid-name 455 | if __name__ == "__main__": 456 | main() 457 | -------------------------------------------------------------------------------- /deepl_tr_async/load_env.py: -------------------------------------------------------------------------------- 1 | from pathlib import Path 2 | import dotenv 3 | from environs import Env 4 | 5 | from logzero import logger 6 | 7 | 8 | def load_env(var="hotkey", attr="str"): 9 | """ 10 | os.environ has priority 11 | cwd / .env 12 | Path(__file__).parent / .env 13 | 14 | load_env("debug", "bool") 15 | load_env("hotkey") 16 | 17 | """ 18 | env = Env() 19 | _ = dotenv.find_dotenv(Path.cwd() / ".env") 20 | if not _: 21 | _ = dotenv.find_dotenv() 22 | if _: 23 | logger.info("Loading os.environ and .env from\n\t [%s]", _) 24 | env.read_env(_) 25 | else: 26 | logger.info(" No .env file found, os.environ only") 27 | 28 | try: 29 | return getattr(env, attr)(var) # OK 30 | except Exception as exc: 31 | logger.warning('\n\t %s, return empty str ""', exc) 32 | return "" 33 | 34 | 35 | def main(): 36 | """ main """ 37 | print(load_env("hotkey")) 38 | 39 | 40 | if __name__ == "__main__": 41 | main() 42 | -------------------------------------------------------------------------------- /img/copyfrom-false.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ffreemt/deepl-tr-async/d71e5f87c922f99bb40312e286accdf3c76013cb/img/copyfrom-false.png -------------------------------------------------------------------------------- /img/helpfull.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ffreemt/deepl-tr-async/d71e5f87c922f99bb40312e286accdf3c76013cb/img/helpfull.png -------------------------------------------------------------------------------- /mypy.ini: -------------------------------------------------------------------------------- 1 | [mypy] 2 | python_version = 3.6 3 | ignore_missing_imports = True -------------------------------------------------------------------------------- /poetry.lock: -------------------------------------------------------------------------------- 1 | [[package]] 2 | name = "absl-py" 3 | version = "0.9.0" 4 | description = "Abseil Python Common Libraries, see https://github.com/abseil/abseil-py." 5 | category = "main" 6 | optional = false 7 | python-versions = "*" 8 | 9 | [package.dependencies] 10 | six = "*" 11 | 12 | [[package]] 13 | name = "altgraph" 14 | version = "0.17" 15 | description = "Python graph (network) package" 16 | category = "main" 17 | optional = false 18 | python-versions = "*" 19 | 20 | [[package]] 21 | name = "appdirs" 22 | version = "1.4.3" 23 | description = "A small Python module for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." 24 | category = "main" 25 | optional = false 26 | python-versions = "*" 27 | 28 | [[package]] 29 | name = "appnope" 30 | version = "0.1.2" 31 | description = "Disable App Nap on macOS >= 10.9" 32 | category = "main" 33 | optional = false 34 | python-versions = "*" 35 | 36 | [[package]] 37 | name = "atomicwrites" 38 | version = "1.3.0" 39 | description = "Atomic file writes." 40 | category = "main" 41 | optional = false 42 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" 43 | 44 | [[package]] 45 | name = "attrs" 46 | version = "19.3.0" 47 | description = "Classes Without Boilerplate" 48 | category = "main" 49 | optional = false 50 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" 51 | 52 | [package.extras] 53 | azure-pipelines = ["coverage", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "zope.interface", "pytest-azurepipelines"] 54 | dev = ["coverage", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "zope.interface", "sphinx", "pre-commit"] 55 | docs = ["sphinx", "zope.interface"] 56 | tests = ["coverage", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "zope.interface"] 57 | 58 | [[package]] 59 | name = "backcall" 60 | version = "0.2.0" 61 | description = "Specifications for callback functions passed in to an API" 62 | category = "main" 63 | optional = false 64 | python-versions = "*" 65 | 66 | [[package]] 67 | name = "colorama" 68 | version = "0.4.3" 69 | description = "Cross-platform colored terminal text." 70 | category = "main" 71 | optional = false 72 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" 73 | 74 | [[package]] 75 | name = "coverage" 76 | version = "5.0.4" 77 | description = "Code coverage measurement for Python" 78 | category = "main" 79 | optional = false 80 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, <4" 81 | 82 | [package.extras] 83 | toml = ["toml"] 84 | 85 | [[package]] 86 | name = "cssselect" 87 | version = "1.1.0" 88 | description = "cssselect parses CSS3 Selectors and translates them to XPath 1.0" 89 | category = "main" 90 | optional = false 91 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" 92 | 93 | [[package]] 94 | name = "decorator" 95 | version = "4.4.2" 96 | description = "Decorators for Humans" 97 | category = "main" 98 | optional = false 99 | python-versions = ">=2.6, !=3.0.*, !=3.1.*" 100 | 101 | [[package]] 102 | name = "dis3" 103 | version = "0.1.3" 104 | description = "Python 2.7 backport of the \"dis\" module from Python 3.5+" 105 | category = "main" 106 | optional = false 107 | python-versions = "*" 108 | 109 | [[package]] 110 | name = "entrypoints" 111 | version = "0.3" 112 | description = "Discover and load entry points from installed packages." 113 | category = "main" 114 | optional = false 115 | python-versions = ">=2.7" 116 | 117 | [[package]] 118 | name = "environs" 119 | version = "7.3.1" 120 | description = "simplified environment variable parsing" 121 | category = "main" 122 | optional = false 123 | python-versions = ">=3.5" 124 | 125 | [package.dependencies] 126 | marshmallow = ">=2.7.0" 127 | python-dotenv = "*" 128 | 129 | [package.extras] 130 | dev = ["pytest", "dj-database-url", "dj-email-url", "django-cache-url", "flake8 (==3.7.9)", "flake8-bugbear (==20.1.4)", "mypy (==0.770)", "pre-commit (>=1.20,<3.0)", "tox"] 131 | django = ["dj-database-url", "dj-email-url", "django-cache-url"] 132 | lint = ["flake8 (==3.7.9)", "flake8-bugbear (==20.1.4)", "mypy (==0.770)", "pre-commit (>=1.20,<3.0)"] 133 | tests = ["pytest", "dj-database-url", "dj-email-url", "django-cache-url"] 134 | 135 | [[package]] 136 | name = "flake8" 137 | version = "3.7.9" 138 | description = "the modular source code checker: pep8, pyflakes and co" 139 | category = "main" 140 | optional = false 141 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" 142 | 143 | [package.dependencies] 144 | entrypoints = ">=0.3.0,<0.4.0" 145 | mccabe = ">=0.6.0,<0.7.0" 146 | pycodestyle = ">=2.5.0,<2.6.0" 147 | pyflakes = ">=2.1.0,<2.2.0" 148 | 149 | [[package]] 150 | name = "fuzzywuzzy" 151 | version = "0.18.0" 152 | description = "Fuzzy string matching in python" 153 | category = "main" 154 | optional = false 155 | python-versions = "*" 156 | 157 | [package.extras] 158 | speedup = ["python-levenshtein (>=0.12)"] 159 | 160 | [[package]] 161 | name = "get-ppbrowser" 162 | version = "0.1.1" 163 | description = "Create a valid pyppeteer browser" 164 | category = "main" 165 | optional = false 166 | python-versions = ">=3.7,<4.0" 167 | 168 | [package.dependencies] 169 | logzero = ">=1.6.3,<2.0.0" 170 | pydantic = ">=1.7.3,<2.0.0" 171 | pyppeteer2 = ">=0.2.2,<0.3.0" 172 | pytest-asyncio = ">=0.14.0,<0.15.0" 173 | python-dotenv = ">=0.15.0,<0.16.0" 174 | 175 | [[package]] 176 | name = "importlib-metadata" 177 | version = "1.5.0" 178 | description = "Read metadata from Python packages" 179 | category = "main" 180 | optional = false 181 | python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7" 182 | 183 | [package.dependencies] 184 | zipp = ">=0.5" 185 | 186 | [package.extras] 187 | docs = ["sphinx", "rst.linker"] 188 | testing = ["packaging", "importlib-resources"] 189 | 190 | [[package]] 191 | name = "ipython" 192 | version = "7.20.0" 193 | description = "IPython: Productive Interactive Computing" 194 | category = "main" 195 | optional = false 196 | python-versions = ">=3.7" 197 | 198 | [package.dependencies] 199 | appnope = {version = "*", markers = "sys_platform == \"darwin\""} 200 | backcall = "*" 201 | colorama = {version = "*", markers = "sys_platform == \"win32\""} 202 | decorator = "*" 203 | jedi = ">=0.16" 204 | pexpect = {version = ">4.3", markers = "sys_platform != \"win32\""} 205 | pickleshare = "*" 206 | prompt-toolkit = ">=2.0.0,<3.0.0 || >3.0.0,<3.0.1 || >3.0.1,<3.1.0" 207 | pygments = "*" 208 | traitlets = ">=4.2" 209 | 210 | [package.extras] 211 | all = ["Sphinx (>=1.3)", "ipykernel", "ipyparallel", "ipywidgets", "nbconvert", "nbformat", "nose (>=0.10.1)", "notebook", "numpy (>=1.14)", "pygments", "qtconsole", "requests", "testpath"] 212 | doc = ["Sphinx (>=1.3)"] 213 | kernel = ["ipykernel"] 214 | nbconvert = ["nbconvert"] 215 | nbformat = ["nbformat"] 216 | notebook = ["notebook", "ipywidgets"] 217 | parallel = ["ipyparallel"] 218 | qtconsole = ["qtconsole"] 219 | test = ["nose (>=0.10.1)", "requests", "testpath", "pygments", "nbformat", "ipykernel", "numpy (>=1.14)"] 220 | 221 | [[package]] 222 | name = "ipython-genutils" 223 | version = "0.2.0" 224 | description = "Vestigial utilities from IPython" 225 | category = "main" 226 | optional = false 227 | python-versions = "*" 228 | 229 | [[package]] 230 | name = "jedi" 231 | version = "0.18.0" 232 | description = "An autocompletion tool for Python that can be used for text editors." 233 | category = "main" 234 | optional = false 235 | python-versions = ">=3.6" 236 | 237 | [package.dependencies] 238 | parso = ">=0.8.0,<0.9.0" 239 | 240 | [package.extras] 241 | qa = ["flake8 (==3.8.3)", "mypy (==0.782)"] 242 | testing = ["Django (<3.1)", "colorama", "docopt", "pytest (<6.0.0)"] 243 | 244 | [[package]] 245 | name = "linetimer" 246 | version = "0.1.4" 247 | description = "A small Python class to quickly measure the time taken while executing a block of indented lines" 248 | category = "main" 249 | optional = false 250 | python-versions = "*" 251 | 252 | [[package]] 253 | name = "logzero" 254 | version = "1.6.3" 255 | description = "Robust and effective logging for Python 2 and 3" 256 | category = "main" 257 | optional = false 258 | python-versions = "*" 259 | 260 | [package.dependencies] 261 | colorama = {version = "*", markers = "sys_platform == \"win32\""} 262 | 263 | [[package]] 264 | name = "lxml" 265 | version = "4.6.3" 266 | description = "Powerful and Pythonic XML processing library combining libxml2/libxslt with the ElementTree API." 267 | category = "main" 268 | optional = false 269 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, != 3.4.*" 270 | 271 | [package.extras] 272 | cssselect = ["cssselect (>=0.7)"] 273 | html5 = ["html5lib"] 274 | htmlsoup = ["beautifulsoup4"] 275 | source = ["Cython (>=0.29.7)"] 276 | 277 | [[package]] 278 | name = "marshmallow" 279 | version = "3.5.1" 280 | description = "A lightweight library for converting complex datatypes to and from native Python datatypes." 281 | category = "main" 282 | optional = false 283 | python-versions = ">=3.5" 284 | 285 | [package.extras] 286 | dev = ["pytest", "pytz", "simplejson", "mypy (==0.761)", "flake8 (==3.7.9)", "flake8-bugbear (==20.1.4)", "pre-commit (>=1.20,<3.0)", "tox"] 287 | docs = ["sphinx (==2.4.3)", "sphinx-issues (==1.2.0)", "alabaster (==0.7.12)", "sphinx-version-warning (==1.1.2)"] 288 | lint = ["mypy (==0.761)", "flake8 (==3.7.9)", "flake8-bugbear (==20.1.4)", "pre-commit (>=1.20,<3.0)"] 289 | tests = ["pytest", "pytz", "simplejson"] 290 | 291 | [[package]] 292 | name = "mccabe" 293 | version = "0.6.1" 294 | description = "McCabe checker, plugin for flake8" 295 | category = "main" 296 | optional = false 297 | python-versions = "*" 298 | 299 | [[package]] 300 | name = "more-itertools" 301 | version = "8.2.0" 302 | description = "More routines for operating on iterables, beyond itertools" 303 | category = "main" 304 | optional = false 305 | python-versions = ">=3.5" 306 | 307 | [[package]] 308 | name = "packaging" 309 | version = "20.3" 310 | description = "Core utilities for Python packages" 311 | category = "main" 312 | optional = false 313 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" 314 | 315 | [package.dependencies] 316 | pyparsing = ">=2.0.2" 317 | six = "*" 318 | 319 | [[package]] 320 | name = "parso" 321 | version = "0.8.1" 322 | description = "A Python Parser" 323 | category = "main" 324 | optional = false 325 | python-versions = ">=3.6" 326 | 327 | [package.extras] 328 | qa = ["flake8 (==3.8.3)", "mypy (==0.782)"] 329 | testing = ["docopt", "pytest (<6.0.0)"] 330 | 331 | [[package]] 332 | name = "pexpect" 333 | version = "4.8.0" 334 | description = "Pexpect allows easy control of interactive console applications." 335 | category = "main" 336 | optional = false 337 | python-versions = "*" 338 | 339 | [package.dependencies] 340 | ptyprocess = ">=0.5" 341 | 342 | [[package]] 343 | name = "pickleshare" 344 | version = "0.7.5" 345 | description = "Tiny 'shelve'-like database with concurrency support" 346 | category = "main" 347 | optional = false 348 | python-versions = "*" 349 | 350 | [[package]] 351 | name = "pluggy" 352 | version = "0.13.1" 353 | description = "plugin and hook calling mechanisms for python" 354 | category = "main" 355 | optional = false 356 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" 357 | 358 | [package.dependencies] 359 | importlib-metadata = {version = ">=0.12", markers = "python_version < \"3.8\""} 360 | 361 | [package.extras] 362 | dev = ["pre-commit", "tox"] 363 | 364 | [[package]] 365 | name = "polyglot" 366 | version = "16.7.4" 367 | description = "Polyglot is a natural language pipeline that supports massive multilingual applications." 368 | category = "main" 369 | optional = false 370 | python-versions = "*" 371 | 372 | [[package]] 373 | name = "prompt-toolkit" 374 | version = "3.0.16" 375 | description = "Library for building powerful interactive command lines in Python" 376 | category = "main" 377 | optional = false 378 | python-versions = ">=3.6.1" 379 | 380 | [package.dependencies] 381 | wcwidth = "*" 382 | 383 | [[package]] 384 | name = "ptyprocess" 385 | version = "0.7.0" 386 | description = "Run a subprocess in a pseudo terminal" 387 | category = "main" 388 | optional = false 389 | python-versions = "*" 390 | 391 | [[package]] 392 | name = "py" 393 | version = "1.8.1" 394 | description = "library with cross-python path, ini-parsing, io, code, log facilities" 395 | category = "main" 396 | optional = false 397 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" 398 | 399 | [[package]] 400 | name = "pycld2" 401 | version = "0.41" 402 | description = "Python bindings around Google Chromium's embedded compact language detection library (CLD2)" 403 | category = "main" 404 | optional = false 405 | python-versions = "*" 406 | 407 | [[package]] 408 | name = "pycodestyle" 409 | version = "2.5.0" 410 | description = "Python style guide checker" 411 | category = "main" 412 | optional = false 413 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" 414 | 415 | [[package]] 416 | name = "pydantic" 417 | version = "1.7.3" 418 | description = "Data validation and settings management using python 3.6 type hinting" 419 | category = "main" 420 | optional = false 421 | python-versions = ">=3.6" 422 | 423 | [package.extras] 424 | dotenv = ["python-dotenv (>=0.10.4)"] 425 | email = ["email-validator (>=1.0.3)"] 426 | typing_extensions = ["typing-extensions (>=3.7.2)"] 427 | 428 | [[package]] 429 | name = "pyee" 430 | version = "7.0.1" 431 | description = "A port of node.js's EventEmitter to python." 432 | category = "main" 433 | optional = false 434 | python-versions = "*" 435 | 436 | [[package]] 437 | name = "pyflakes" 438 | version = "2.1.1" 439 | description = "passive checker of Python programs" 440 | category = "main" 441 | optional = false 442 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" 443 | 444 | [[package]] 445 | name = "pygments" 446 | version = "2.8.0" 447 | description = "Pygments is a syntax highlighting package written in Python." 448 | category = "main" 449 | optional = false 450 | python-versions = ">=3.5" 451 | 452 | [[package]] 453 | name = "pyicu" 454 | version = "2.6" 455 | description = "Python extension wrapping the ICU C++ API" 456 | category = "main" 457 | optional = false 458 | python-versions = "*" 459 | 460 | [[package]] 461 | name = "pyinstaller" 462 | version = "3.6" 463 | description = "PyInstaller bundles a Python application and all its dependencies into a single package." 464 | category = "main" 465 | optional = false 466 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" 467 | 468 | [package.dependencies] 469 | altgraph = "*" 470 | dis3 = "*" 471 | 472 | [[package]] 473 | name = "pyparsing" 474 | version = "2.4.6" 475 | description = "Python parsing module" 476 | category = "main" 477 | optional = false 478 | python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" 479 | 480 | [[package]] 481 | name = "pyperclip" 482 | version = "1.7.0" 483 | description = "A cross-platform clipboard module for Python. (Only handles plain text for now.)" 484 | category = "main" 485 | optional = false 486 | python-versions = "*" 487 | 488 | [[package]] 489 | name = "pyppeteer2" 490 | version = "0.2.2" 491 | description = "Headless chrome/chromium automation library (unofficial port of puppeteer)" 492 | category = "main" 493 | optional = false 494 | python-versions = ">=3.6.1,<4.0.0" 495 | 496 | [package.dependencies] 497 | appdirs = ">=1.4.3,<2.0.0" 498 | pyee = ">=7.0.1,<8.0.0" 499 | tqdm = ">=4.42.1,<5.0.0" 500 | urllib3 = ">=1.25.8,<2.0.0" 501 | websockets = ">=8.1,<9.0" 502 | 503 | [[package]] 504 | name = "pyquery" 505 | version = "1.4.1" 506 | description = "A jquery-like library for python" 507 | category = "main" 508 | optional = false 509 | python-versions = "*" 510 | 511 | [package.dependencies] 512 | cssselect = ">0.7.9" 513 | lxml = ">=2.1" 514 | 515 | [[package]] 516 | name = "pytest" 517 | version = "5.4.1" 518 | description = "pytest: simple powerful testing with Python" 519 | category = "main" 520 | optional = false 521 | python-versions = ">=3.5" 522 | 523 | [package.dependencies] 524 | atomicwrites = {version = ">=1.0", markers = "sys_platform == \"win32\""} 525 | attrs = ">=17.4.0" 526 | colorama = {version = "*", markers = "sys_platform == \"win32\""} 527 | importlib-metadata = {version = ">=0.12", markers = "python_version < \"3.8\""} 528 | more-itertools = ">=4.0.0" 529 | packaging = "*" 530 | pluggy = ">=0.12,<1.0" 531 | py = ">=1.5.0" 532 | wcwidth = "*" 533 | 534 | [package.extras] 535 | checkqa-mypy = ["mypy (==v0.761)"] 536 | testing = ["argcomplete", "hypothesis (>=3.56)", "mock", "nose", "requests", "xmlschema"] 537 | 538 | [[package]] 539 | name = "pytest-asyncio" 540 | version = "0.14.0" 541 | description = "Pytest support for asyncio." 542 | category = "main" 543 | optional = false 544 | python-versions = ">= 3.5" 545 | 546 | [package.dependencies] 547 | pytest = ">=5.4.0" 548 | 549 | [package.extras] 550 | testing = ["async-generator (>=1.3)", "coverage", "hypothesis (>=5.7.1)"] 551 | 552 | [[package]] 553 | name = "pytest-cov" 554 | version = "2.8.1" 555 | description = "Pytest plugin for measuring coverage." 556 | category = "main" 557 | optional = false 558 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" 559 | 560 | [package.dependencies] 561 | coverage = ">=4.4" 562 | pytest = ">=3.6" 563 | 564 | [package.extras] 565 | testing = ["fields", "hunter", "process-tests (==2.0.2)", "six", "virtualenv"] 566 | 567 | [[package]] 568 | name = "python-dotenv" 569 | version = "0.15.0" 570 | description = "Add .env support to your django/flask apps in development and deployments" 571 | category = "main" 572 | optional = false 573 | python-versions = "*" 574 | 575 | [package.extras] 576 | cli = ["click (>=5.0)"] 577 | 578 | [[package]] 579 | name = "six" 580 | version = "1.14.0" 581 | description = "Python 2 and 3 compatibility utilities" 582 | category = "main" 583 | optional = false 584 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" 585 | 586 | [[package]] 587 | name = "tqdm" 588 | version = "4.43.0" 589 | description = "Fast, Extensible Progress Meter" 590 | category = "main" 591 | optional = false 592 | python-versions = ">=2.6, !=3.0.*, !=3.1.*" 593 | 594 | [package.extras] 595 | dev = ["py-make (>=0.1.0)", "twine", "argopt", "pydoc-markdown"] 596 | 597 | [[package]] 598 | name = "traitlets" 599 | version = "5.0.5" 600 | description = "Traitlets Python configuration system" 601 | category = "main" 602 | optional = false 603 | python-versions = ">=3.7" 604 | 605 | [package.dependencies] 606 | ipython-genutils = "*" 607 | 608 | [package.extras] 609 | test = ["pytest"] 610 | 611 | [[package]] 612 | name = "urllib3" 613 | version = "1.25.9" 614 | description = "HTTP library with thread-safe connection pooling, file post, and more." 615 | category = "main" 616 | optional = false 617 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, <4" 618 | 619 | [package.extras] 620 | brotli = ["brotlipy (>=0.6.0)"] 621 | secure = ["certifi", "cryptography (>=1.3.4)", "idna (>=2.0.0)", "pyOpenSSL (>=0.14)", "ipaddress"] 622 | socks = ["PySocks (>=1.5.6,!=1.5.7,<2.0)"] 623 | 624 | [[package]] 625 | name = "wcwidth" 626 | version = "0.1.8" 627 | description = "Measures number of Terminal column cells of wide-character codes" 628 | category = "main" 629 | optional = false 630 | python-versions = "*" 631 | 632 | [[package]] 633 | name = "websockets" 634 | version = "8.1" 635 | description = "An implementation of the WebSocket Protocol (RFC 6455 & 7692)" 636 | category = "main" 637 | optional = false 638 | python-versions = ">=3.6.1" 639 | 640 | [[package]] 641 | name = "zipp" 642 | version = "3.1.0" 643 | description = "Backport of pathlib-compatible object wrapper for zip files" 644 | category = "main" 645 | optional = false 646 | python-versions = ">=3.6" 647 | 648 | [package.extras] 649 | docs = ["sphinx", "jaraco.packaging (>=3.2)", "rst.linker (>=1.9)"] 650 | testing = ["jaraco.itertools", "func-timeout"] 651 | 652 | [metadata] 653 | lock-version = "1.1" 654 | python-versions = "^3.7" 655 | content-hash = "bf26444672c8c205b6e0b125b5ea1674e82de75223f1591099b3d9348ffc7915" 656 | 657 | [metadata.files] 658 | absl-py = [ 659 | {file = "absl-py-0.9.0.tar.gz", hash = "sha256:75e737d6ce7723d9ff9b7aa1ba3233c34be62ef18d5859e706b8fdc828989830"}, 660 | ] 661 | altgraph = [ 662 | {file = "altgraph-0.17-py2.py3-none-any.whl", hash = "sha256:c623e5f3408ca61d4016f23a681b9adb100802ca3e3da5e718915a9e4052cebe"}, 663 | {file = "altgraph-0.17.tar.gz", hash = "sha256:1f05a47122542f97028caf78775a095fbe6a2699b5089de8477eb583167d69aa"}, 664 | ] 665 | appdirs = [ 666 | {file = "appdirs-1.4.3-py2.py3-none-any.whl", hash = "sha256:d8b24664561d0d34ddfaec54636d502d7cea6e29c3eaf68f3df6180863e2166e"}, 667 | {file = "appdirs-1.4.3.tar.gz", hash = "sha256:9e5896d1372858f8dd3344faf4e5014d21849c756c8d5701f78f8a103b372d92"}, 668 | ] 669 | appnope = [ 670 | {file = "appnope-0.1.2-py2.py3-none-any.whl", hash = "sha256:93aa393e9d6c54c5cd570ccadd8edad61ea0c4b9ea7a01409020c9aa019eb442"}, 671 | {file = "appnope-0.1.2.tar.gz", hash = "sha256:dd83cd4b5b460958838f6eb3000c660b1f9caf2a5b1de4264e941512f603258a"}, 672 | ] 673 | atomicwrites = [ 674 | {file = "atomicwrites-1.3.0-py2.py3-none-any.whl", hash = "sha256:03472c30eb2c5d1ba9227e4c2ca66ab8287fbfbbda3888aa93dc2e28fc6811b4"}, 675 | {file = "atomicwrites-1.3.0.tar.gz", hash = "sha256:75a9445bac02d8d058d5e1fe689654ba5a6556a1dfd8ce6ec55a0ed79866cfa6"}, 676 | ] 677 | attrs = [ 678 | {file = "attrs-19.3.0-py2.py3-none-any.whl", hash = "sha256:08a96c641c3a74e44eb59afb61a24f2cb9f4d7188748e76ba4bb5edfa3cb7d1c"}, 679 | {file = "attrs-19.3.0.tar.gz", hash = "sha256:f7b7ce16570fe9965acd6d30101a28f62fb4a7f9e926b3bbc9b61f8b04247e72"}, 680 | ] 681 | backcall = [ 682 | {file = "backcall-0.2.0-py2.py3-none-any.whl", hash = "sha256:fbbce6a29f263178a1f7915c1940bde0ec2b2a967566fe1c65c1dfb7422bd255"}, 683 | {file = "backcall-0.2.0.tar.gz", hash = "sha256:5cbdbf27be5e7cfadb448baf0aa95508f91f2bbc6c6437cd9cd06e2a4c215e1e"}, 684 | ] 685 | colorama = [ 686 | {file = "colorama-0.4.3-py2.py3-none-any.whl", hash = "sha256:7d73d2a99753107a36ac6b455ee49046802e59d9d076ef8e47b61499fa29afff"}, 687 | {file = "colorama-0.4.3.tar.gz", hash = "sha256:e96da0d330793e2cb9485e9ddfd918d456036c7149416295932478192f4436a1"}, 688 | ] 689 | coverage = [ 690 | {file = "coverage-5.0.4-cp27-cp27m-macosx_10_12_x86_64.whl", hash = "sha256:8a620767b8209f3446197c0e29ba895d75a1e272a36af0786ec70fe7834e4307"}, 691 | {file = "coverage-5.0.4-cp27-cp27m-macosx_10_13_intel.whl", hash = "sha256:73aa6e86034dad9f00f4bbf5a666a889d17d79db73bc5af04abd6c20a014d9c8"}, 692 | {file = "coverage-5.0.4-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:408ce64078398b2ee2ec08199ea3fcf382828d2f8a19c5a5ba2946fe5ddc6c31"}, 693 | {file = "coverage-5.0.4-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:cda33311cb9fb9323958a69499a667bd728a39a7aa4718d7622597a44c4f1441"}, 694 | {file = "coverage-5.0.4-cp27-cp27m-win32.whl", hash = "sha256:5f587dfd83cb669933186661a351ad6fc7166273bc3e3a1531ec5c783d997aac"}, 695 | {file = "coverage-5.0.4-cp27-cp27m-win_amd64.whl", hash = "sha256:9fad78c13e71546a76c2f8789623eec8e499f8d2d799f4b4547162ce0a4df435"}, 696 | {file = "coverage-5.0.4-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:2e08c32cbede4a29e2a701822291ae2bc9b5220a971bba9d1e7615312efd3037"}, 697 | {file = "coverage-5.0.4-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:922fb9ef2c67c3ab20e22948dcfd783397e4c043a5c5fa5ff5e9df5529074b0a"}, 698 | {file = "coverage-5.0.4-cp35-cp35m-macosx_10_12_x86_64.whl", hash = "sha256:c3fc325ce4cbf902d05a80daa47b645d07e796a80682c1c5800d6ac5045193e5"}, 699 | {file = "coverage-5.0.4-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:046a1a742e66d065d16fb564a26c2a15867f17695e7f3d358d7b1ad8a61bca30"}, 700 | {file = "coverage-5.0.4-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:6ad6ca45e9e92c05295f638e78cd42bfaaf8ee07878c9ed73e93190b26c125f7"}, 701 | {file = "coverage-5.0.4-cp35-cp35m-win32.whl", hash = "sha256:eda55e6e9ea258f5e4add23bcf33dc53b2c319e70806e180aecbff8d90ea24de"}, 702 | {file = "coverage-5.0.4-cp35-cp35m-win_amd64.whl", hash = "sha256:4a8a259bf990044351baf69d3b23e575699dd60b18460c71e81dc565f5819ac1"}, 703 | {file = "coverage-5.0.4-cp36-cp36m-macosx_10_13_x86_64.whl", hash = "sha256:f372cdbb240e09ee855735b9d85e7f50730dcfb6296b74b95a3e5dea0615c4c1"}, 704 | {file = "coverage-5.0.4-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:a37c6233b28e5bc340054cf6170e7090a4e85069513320275a4dc929144dccf0"}, 705 | {file = "coverage-5.0.4-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:443be7602c790960b9514567917af538cac7807a7c0c0727c4d2bbd4014920fd"}, 706 | {file = "coverage-5.0.4-cp36-cp36m-win32.whl", hash = "sha256:165a48268bfb5a77e2d9dbb80de7ea917332a79c7adb747bd005b3a07ff8caf0"}, 707 | {file = "coverage-5.0.4-cp36-cp36m-win_amd64.whl", hash = "sha256:0a907199566269e1cfa304325cc3b45c72ae341fbb3253ddde19fa820ded7a8b"}, 708 | {file = "coverage-5.0.4-cp37-cp37m-macosx_10_13_x86_64.whl", hash = "sha256:513e6526e0082c59a984448f4104c9bf346c2da9961779ede1fc458e8e8a1f78"}, 709 | {file = "coverage-5.0.4-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:3844c3dab800ca8536f75ae89f3cf566848a3eb2af4d9f7b1103b4f4f7a5dad6"}, 710 | {file = "coverage-5.0.4-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:641e329e7f2c01531c45c687efcec8aeca2a78a4ff26d49184dce3d53fc35014"}, 711 | {file = "coverage-5.0.4-cp37-cp37m-win32.whl", hash = "sha256:db1d4e38c9b15be1521722e946ee24f6db95b189d1447fa9ff18dd16ba89f732"}, 712 | {file = "coverage-5.0.4-cp37-cp37m-win_amd64.whl", hash = "sha256:62061e87071497951155cbccee487980524d7abea647a1b2a6eb6b9647df9006"}, 713 | {file = "coverage-5.0.4-cp38-cp38-macosx_10_13_x86_64.whl", hash = "sha256:65a7e00c00472cd0f59ae09d2fb8a8aaae7f4a0cf54b2b74f3138d9f9ceb9cb2"}, 714 | {file = "coverage-5.0.4-cp38-cp38-manylinux1_i686.whl", hash = "sha256:1f66cf263ec77af5b8fe14ef14c5e46e2eb4a795ac495ad7c03adc72ae43fafe"}, 715 | {file = "coverage-5.0.4-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:85596aa5d9aac1bf39fe39d9fa1051b0f00823982a1de5766e35d495b4a36ca9"}, 716 | {file = "coverage-5.0.4-cp38-cp38-win32.whl", hash = "sha256:86a0ea78fd851b313b2e712266f663e13b6bc78c2fb260b079e8b67d970474b1"}, 717 | {file = "coverage-5.0.4-cp38-cp38-win_amd64.whl", hash = "sha256:03f630aba2b9b0d69871c2e8d23a69b7fe94a1e2f5f10df5049c0df99db639a0"}, 718 | {file = "coverage-5.0.4-cp39-cp39-win32.whl", hash = "sha256:7c9762f80a25d8d0e4ab3cb1af5d9dffbddb3ee5d21c43e3474c84bf5ff941f7"}, 719 | {file = "coverage-5.0.4-cp39-cp39-win_amd64.whl", hash = "sha256:4482f69e0701139d0f2c44f3c395d1d1d37abd81bfafbf9b6efbe2542679d892"}, 720 | {file = "coverage-5.0.4.tar.gz", hash = "sha256:1b60a95fc995649464e0cd48cecc8288bac5f4198f21d04b8229dc4097d76823"}, 721 | ] 722 | cssselect = [ 723 | {file = "cssselect-1.1.0-py2.py3-none-any.whl", hash = "sha256:f612ee47b749c877ebae5bb77035d8f4202c6ad0f0fc1271b3c18ad6c4468ecf"}, 724 | {file = "cssselect-1.1.0.tar.gz", hash = "sha256:f95f8dedd925fd8f54edb3d2dfb44c190d9d18512377d3c1e2388d16126879bc"}, 725 | ] 726 | decorator = [ 727 | {file = "decorator-4.4.2-py2.py3-none-any.whl", hash = "sha256:41fa54c2a0cc4ba648be4fd43cff00aedf5b9465c9bf18d64325bc225f08f760"}, 728 | {file = "decorator-4.4.2.tar.gz", hash = "sha256:e3a62f0520172440ca0dcc823749319382e377f37f140a0b99ef45fecb84bfe7"}, 729 | ] 730 | dis3 = [ 731 | {file = "dis3-0.1.3-py2-none-any.whl", hash = "sha256:61f7720dd0d8749d23fda3d7227ce74d73da11c2fade993a67ab2f9852451b14"}, 732 | {file = "dis3-0.1.3-py3-none-any.whl", hash = "sha256:30b6412d33d738663e8ded781b138f4b01116437f0872aa56aa3adba6aeff218"}, 733 | {file = "dis3-0.1.3.tar.gz", hash = "sha256:9259b881fc1df02ed12ac25f82d4a85b44241854330b1a651e40e0c675cb2d1e"}, 734 | ] 735 | entrypoints = [ 736 | {file = "entrypoints-0.3-py2.py3-none-any.whl", hash = "sha256:589f874b313739ad35be6e0cd7efde2a4e9b6fea91edcc34e58ecbb8dbe56d19"}, 737 | {file = "entrypoints-0.3.tar.gz", hash = "sha256:c70dd71abe5a8c85e55e12c19bd91ccfeec11a6e99044204511f9ed547d48451"}, 738 | ] 739 | environs = [ 740 | {file = "environs-7.3.1-py2.py3-none-any.whl", hash = "sha256:9502d78a3b7ea23c211c3a88f4f946d188a7d936c59c3609f9303aeb26cdc58d"}, 741 | {file = "environs-7.3.1.tar.gz", hash = "sha256:68d53c4bb283d8008865815226afba83ab2ea09898e564f3399c9e73b3d4bcfd"}, 742 | ] 743 | flake8 = [ 744 | {file = "flake8-3.7.9-py2.py3-none-any.whl", hash = "sha256:49356e766643ad15072a789a20915d3c91dc89fd313ccd71802303fd67e4deca"}, 745 | {file = "flake8-3.7.9.tar.gz", hash = "sha256:45681a117ecc81e870cbf1262835ae4af5e7a8b08e40b944a8a6e6b895914cfb"}, 746 | ] 747 | fuzzywuzzy = [ 748 | {file = "fuzzywuzzy-0.18.0-py2.py3-none-any.whl", hash = "sha256:928244b28db720d1e0ee7587acf660ea49d7e4c632569cad4f1cd7e68a5f0993"}, 749 | {file = "fuzzywuzzy-0.18.0.tar.gz", hash = "sha256:45016e92264780e58972dca1b3d939ac864b78437422beecebb3095f8efd00e8"}, 750 | ] 751 | get-ppbrowser = [ 752 | {file = "get-ppbrowser-0.1.1.tar.gz", hash = "sha256:5dbb2188d88bc9a9f23ab0415c89719682f319f41d1f6c88d6a1034512ccab16"}, 753 | {file = "get_ppbrowser-0.1.1-py3-none-any.whl", hash = "sha256:d67eef3999e45d963720c16580b02fb296b2ccd50a75aaf9ff46d9e40cdfdf38"}, 754 | ] 755 | importlib-metadata = [ 756 | {file = "importlib_metadata-1.5.0-py2.py3-none-any.whl", hash = "sha256:b97607a1a18a5100839aec1dc26a1ea17ee0d93b20b0f008d80a5a050afb200b"}, 757 | {file = "importlib_metadata-1.5.0.tar.gz", hash = "sha256:06f5b3a99029c7134207dd882428a66992a9de2bef7c2b699b5641f9886c3302"}, 758 | ] 759 | ipython = [ 760 | {file = "ipython-7.20.0-py3-none-any.whl", hash = "sha256:1918dea4bfdc5d1a830fcfce9a710d1d809cbed123e85eab0539259cb0f56640"}, 761 | {file = "ipython-7.20.0.tar.gz", hash = "sha256:1923af00820a8cf58e91d56b89efc59780a6e81363b94464a0f17c039dffff9e"}, 762 | ] 763 | ipython-genutils = [ 764 | {file = "ipython_genutils-0.2.0-py2.py3-none-any.whl", hash = "sha256:72dd37233799e619666c9f639a9da83c34013a73e8bbc79a7a6348d93c61fab8"}, 765 | {file = "ipython_genutils-0.2.0.tar.gz", hash = "sha256:eb2e116e75ecef9d4d228fdc66af54269afa26ab4463042e33785b887c628ba8"}, 766 | ] 767 | jedi = [ 768 | {file = "jedi-0.18.0-py2.py3-none-any.whl", hash = "sha256:18456d83f65f400ab0c2d3319e48520420ef43b23a086fdc05dff34132f0fb93"}, 769 | {file = "jedi-0.18.0.tar.gz", hash = "sha256:92550a404bad8afed881a137ec9a461fed49eca661414be45059329614ed0707"}, 770 | ] 771 | linetimer = [ 772 | {file = "linetimer-0.1.4-py2-none-any.whl", hash = "sha256:19ce9c6e6758873ed6c6260b81d7e902e97205f2775671285d404cb1d484d9fa"}, 773 | {file = "linetimer-0.1.4-py3-none-any.whl", hash = "sha256:7816ca0607d7f00662232cf06faccadc2494cbf59c81cf5c714cfac8a91cb35c"}, 774 | ] 775 | logzero = [ 776 | {file = "logzero-1.6.3-py2.py3-none-any.whl", hash = "sha256:1b84ee4c8fdabf7023877ff17cb456d82564097704eb6c4ee37952bd8ce0800f"}, 777 | {file = "logzero-1.6.3.tar.gz", hash = "sha256:1435284574e409b8ec8b680f276bca04cab41f93d6eff4dc8348b7630cddf560"}, 778 | ] 779 | lxml = [ 780 | {file = "lxml-4.6.3-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:df7c53783a46febb0e70f6b05df2ba104610f2fb0d27023409734a3ecbb78fb2"}, 781 | {file = "lxml-4.6.3-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:1b7584d421d254ab86d4f0b13ec662a9014397678a7c4265a02a6d7c2b18a75f"}, 782 | {file = "lxml-4.6.3-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:079f3ae844f38982d156efce585bc540c16a926d4436712cf4baee0cce487a3d"}, 783 | {file = "lxml-4.6.3-cp27-cp27m-win32.whl", hash = "sha256:bc4313cbeb0e7a416a488d72f9680fffffc645f8a838bd2193809881c67dd106"}, 784 | {file = "lxml-4.6.3-cp27-cp27m-win_amd64.whl", hash = "sha256:8157dadbb09a34a6bd95a50690595e1fa0af1a99445e2744110e3dca7831c4ee"}, 785 | {file = "lxml-4.6.3-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:7728e05c35412ba36d3e9795ae8995e3c86958179c9770e65558ec3fdfd3724f"}, 786 | {file = "lxml-4.6.3-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:4bff24dfeea62f2e56f5bab929b4428ae6caba2d1eea0c2d6eb618e30a71e6d4"}, 787 | {file = "lxml-4.6.3-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:74f7d8d439b18fa4c385f3f5dfd11144bb87c1da034a466c5b5577d23a1d9b51"}, 788 | {file = "lxml-4.6.3-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:f90ba11136bfdd25cae3951af8da2e95121c9b9b93727b1b896e3fa105b2f586"}, 789 | {file = "lxml-4.6.3-cp35-cp35m-win32.whl", hash = "sha256:f2380a6376dfa090227b663f9678150ef27543483055cc327555fb592c5967e2"}, 790 | {file = "lxml-4.6.3-cp35-cp35m-win_amd64.whl", hash = "sha256:c4f05c5a7c49d2fb70223d0d5bcfbe474cf928310ac9fa6a7c6dddc831d0b1d4"}, 791 | {file = "lxml-4.6.3-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:d2e35d7bf1c1ac8c538f88d26b396e73dd81440d59c1ef8522e1ea77b345ede4"}, 792 | {file = "lxml-4.6.3-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:289e9ca1a9287f08daaf796d96e06cb2bc2958891d7911ac7cae1c5f9e1e0ee3"}, 793 | {file = "lxml-4.6.3-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:bccbfc27563652de7dc9bdc595cb25e90b59c5f8e23e806ed0fd623755b6565d"}, 794 | {file = "lxml-4.6.3-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:820628b7b3135403540202e60551e741f9b6d3304371712521be939470b454ec"}, 795 | {file = "lxml-4.6.3-cp36-cp36m-win32.whl", hash = "sha256:5a0a14e264069c03e46f926be0d8919f4105c1623d620e7ec0e612a2e9bf1c04"}, 796 | {file = "lxml-4.6.3-cp36-cp36m-win_amd64.whl", hash = "sha256:92e821e43ad382332eade6812e298dc9701c75fe289f2a2d39c7960b43d1e92a"}, 797 | {file = "lxml-4.6.3-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:efd7a09678fd8b53117f6bae4fa3825e0a22b03ef0a932e070c0bdbb3a35e654"}, 798 | {file = "lxml-4.6.3-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:efac139c3f0bf4f0939f9375af4b02c5ad83a622de52d6dfa8e438e8e01d0eb0"}, 799 | {file = "lxml-4.6.3-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:0fbcf5565ac01dff87cbfc0ff323515c823081c5777a9fc7703ff58388c258c3"}, 800 | {file = "lxml-4.6.3-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:122fba10466c7bd4178b07dba427aa516286b846b2cbd6f6169141917283aae2"}, 801 | {file = "lxml-4.6.3-cp37-cp37m-win32.whl", hash = "sha256:3439c71103ef0e904ea0a1901611863e51f50b5cd5e8654a151740fde5e1cade"}, 802 | {file = "lxml-4.6.3-cp37-cp37m-win_amd64.whl", hash = "sha256:4289728b5e2000a4ad4ab8da6e1db2e093c63c08bdc0414799ee776a3f78da4b"}, 803 | {file = "lxml-4.6.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:b007cbb845b28db4fb8b6a5cdcbf65bacb16a8bd328b53cbc0698688a68e1caa"}, 804 | {file = "lxml-4.6.3-cp38-cp38-manylinux1_i686.whl", hash = "sha256:76fa7b1362d19f8fbd3e75fe2fb7c79359b0af8747e6f7141c338f0bee2f871a"}, 805 | {file = "lxml-4.6.3-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:26e761ab5b07adf5f555ee82fb4bfc35bf93750499c6c7614bd64d12aaa67927"}, 806 | {file = "lxml-4.6.3-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:66e575c62792c3f9ca47cb8b6fab9e35bab91360c783d1606f758761810c9791"}, 807 | {file = "lxml-4.6.3-cp38-cp38-win32.whl", hash = "sha256:89b8b22a5ff72d89d48d0e62abb14340d9e99fd637d046c27b8b257a01ffbe28"}, 808 | {file = "lxml-4.6.3-cp38-cp38-win_amd64.whl", hash = "sha256:2a9d50e69aac3ebee695424f7dbd7b8c6d6eb7de2a2eb6b0f6c7db6aa41e02b7"}, 809 | {file = "lxml-4.6.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:ce256aaa50f6cc9a649c51be3cd4ff142d67295bfc4f490c9134d0f9f6d58ef0"}, 810 | {file = "lxml-4.6.3-cp39-cp39-manylinux1_i686.whl", hash = "sha256:7610b8c31688f0b1be0ef882889817939490a36d0ee880ea562a4e1399c447a1"}, 811 | {file = "lxml-4.6.3-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:f8380c03e45cf09f8557bdaa41e1fa7c81f3ae22828e1db470ab2a6c96d8bc23"}, 812 | {file = "lxml-4.6.3-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:884ab9b29feaca361f7f88d811b1eea9bfca36cf3da27768d28ad45c3ee6f969"}, 813 | {file = "lxml-4.6.3-cp39-cp39-win32.whl", hash = "sha256:33bb934a044cf32157c12bfcfbb6649807da20aa92c062ef51903415c704704f"}, 814 | {file = "lxml-4.6.3-cp39-cp39-win_amd64.whl", hash = "sha256:542d454665a3e277f76954418124d67516c5f88e51a900365ed54a9806122b83"}, 815 | {file = "lxml-4.6.3.tar.gz", hash = "sha256:39b78571b3b30645ac77b95f7c69d1bffc4cf8c3b157c435a34da72e78c82468"}, 816 | ] 817 | marshmallow = [ 818 | {file = "marshmallow-3.5.1-py2.py3-none-any.whl", hash = "sha256:ac2e13b30165501b7d41fc0371b8df35944f5849769d136f20e2c5f6cdc6e665"}, 819 | {file = "marshmallow-3.5.1.tar.gz", hash = "sha256:90854221bbb1498d003a0c3cc9d8390259137551917961c8b5258c64026b2f85"}, 820 | ] 821 | mccabe = [ 822 | {file = "mccabe-0.6.1-py2.py3-none-any.whl", hash = "sha256:ab8a6258860da4b6677da4bd2fe5dc2c659cff31b3ee4f7f5d64e79735b80d42"}, 823 | {file = "mccabe-0.6.1.tar.gz", hash = "sha256:dd8d182285a0fe56bace7f45b5e7d1a6ebcbf524e8f3bd87eb0f125271b8831f"}, 824 | ] 825 | more-itertools = [ 826 | {file = "more-itertools-8.2.0.tar.gz", hash = "sha256:b1ddb932186d8a6ac451e1d95844b382f55e12686d51ca0c68b6f61f2ab7a507"}, 827 | {file = "more_itertools-8.2.0-py3-none-any.whl", hash = "sha256:5dd8bcf33e5f9513ffa06d5ad33d78f31e1931ac9a18f33d37e77a180d393a7c"}, 828 | ] 829 | packaging = [ 830 | {file = "packaging-20.3-py2.py3-none-any.whl", hash = "sha256:82f77b9bee21c1bafbf35a84905d604d5d1223801d639cf3ed140bd651c08752"}, 831 | {file = "packaging-20.3.tar.gz", hash = "sha256:3c292b474fda1671ec57d46d739d072bfd495a4f51ad01a055121d81e952b7a3"}, 832 | ] 833 | parso = [ 834 | {file = "parso-0.8.1-py2.py3-none-any.whl", hash = "sha256:15b00182f472319383252c18d5913b69269590616c947747bc50bf4ac768f410"}, 835 | {file = "parso-0.8.1.tar.gz", hash = "sha256:8519430ad07087d4c997fda3a7918f7cfa27cb58972a8c89c2a0295a1c940e9e"}, 836 | ] 837 | pexpect = [ 838 | {file = "pexpect-4.8.0-py2.py3-none-any.whl", hash = "sha256:0b48a55dcb3c05f3329815901ea4fc1537514d6ba867a152b581d69ae3710937"}, 839 | {file = "pexpect-4.8.0.tar.gz", hash = "sha256:fc65a43959d153d0114afe13997d439c22823a27cefceb5ff35c2178c6784c0c"}, 840 | ] 841 | pickleshare = [ 842 | {file = "pickleshare-0.7.5-py2.py3-none-any.whl", hash = "sha256:9649af414d74d4df115d5d718f82acb59c9d418196b7b4290ed47a12ce62df56"}, 843 | {file = "pickleshare-0.7.5.tar.gz", hash = "sha256:87683d47965c1da65cdacaf31c8441d12b8044cdec9aca500cd78fc2c683afca"}, 844 | ] 845 | pluggy = [ 846 | {file = "pluggy-0.13.1-py2.py3-none-any.whl", hash = "sha256:966c145cd83c96502c3c3868f50408687b38434af77734af1e9ca461a4081d2d"}, 847 | {file = "pluggy-0.13.1.tar.gz", hash = "sha256:15b2acde666561e1298d71b523007ed7364de07029219b604cf808bfa1c765b0"}, 848 | ] 849 | polyglot = [ 850 | {file = "polyglot-16.7.4.tar.gz", hash = "sha256:f7d9cca9a212622548e9416fb89f1238b994b8860ef49e03b7c82c67f9b6269b"}, 851 | ] 852 | prompt-toolkit = [ 853 | {file = "prompt_toolkit-3.0.16-py3-none-any.whl", hash = "sha256:62c811e46bd09130fb11ab759012a4ae385ce4fb2073442d1898867a824183bd"}, 854 | {file = "prompt_toolkit-3.0.16.tar.gz", hash = "sha256:0fa02fa80363844a4ab4b8d6891f62dd0645ba672723130423ca4037b80c1974"}, 855 | ] 856 | ptyprocess = [ 857 | {file = "ptyprocess-0.7.0-py2.py3-none-any.whl", hash = "sha256:4b41f3967fce3af57cc7e94b888626c18bf37a083e3651ca8feeb66d492fef35"}, 858 | {file = "ptyprocess-0.7.0.tar.gz", hash = "sha256:5c5d0a3b48ceee0b48485e0c26037c0acd7d29765ca3fbb5cb3831d347423220"}, 859 | ] 860 | py = [ 861 | {file = "py-1.8.1-py2.py3-none-any.whl", hash = "sha256:c20fdd83a5dbc0af9efd622bee9a5564e278f6380fffcacc43ba6f43db2813b0"}, 862 | {file = "py-1.8.1.tar.gz", hash = "sha256:5e27081401262157467ad6e7f851b7aa402c5852dbcb3dae06768434de5752aa"}, 863 | ] 864 | pycld2 = [ 865 | {file = "pycld2-0.41.tar.gz", hash = "sha256:a42f6e974df8fdd70685c2baa8a9f523069a260e1140ce604fb9f1fb6c3064df"}, 866 | ] 867 | pycodestyle = [ 868 | {file = "pycodestyle-2.5.0-py2.py3-none-any.whl", hash = "sha256:95a2219d12372f05704562a14ec30bc76b05a5b297b21a5dfe3f6fac3491ae56"}, 869 | {file = "pycodestyle-2.5.0.tar.gz", hash = "sha256:e40a936c9a450ad81df37f549d676d127b1b66000a6c500caa2b085bc0ca976c"}, 870 | ] 871 | pydantic = [ 872 | {file = "pydantic-1.7.3-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:c59ea046aea25be14dc22d69c97bee629e6d48d2b2ecb724d7fe8806bf5f61cd"}, 873 | {file = "pydantic-1.7.3-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:a4143c8d0c456a093387b96e0f5ee941a950992904d88bc816b4f0e72c9a0009"}, 874 | {file = "pydantic-1.7.3-cp36-cp36m-manylinux2014_i686.whl", hash = "sha256:d8df4b9090b595511906fa48deda47af04e7d092318bfb291f4d45dfb6bb2127"}, 875 | {file = "pydantic-1.7.3-cp36-cp36m-manylinux2014_x86_64.whl", hash = "sha256:514b473d264671a5c672dfb28bdfe1bf1afd390f6b206aa2ec9fed7fc592c48e"}, 876 | {file = "pydantic-1.7.3-cp36-cp36m-win_amd64.whl", hash = "sha256:dba5c1f0a3aeea5083e75db9660935da90216f8a81b6d68e67f54e135ed5eb23"}, 877 | {file = "pydantic-1.7.3-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:59e45f3b694b05a69032a0d603c32d453a23f0de80844fb14d55ab0c6c78ff2f"}, 878 | {file = "pydantic-1.7.3-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:5b24e8a572e4b4c18f614004dda8c9f2c07328cb5b6e314d6e1bbd536cb1a6c1"}, 879 | {file = "pydantic-1.7.3-cp37-cp37m-manylinux2014_i686.whl", hash = "sha256:b2b054d095b6431cdda2f852a6d2f0fdec77686b305c57961b4c5dd6d863bf3c"}, 880 | {file = "pydantic-1.7.3-cp37-cp37m-manylinux2014_x86_64.whl", hash = "sha256:025bf13ce27990acc059d0c5be46f416fc9b293f45363b3d19855165fee1874f"}, 881 | {file = "pydantic-1.7.3-cp37-cp37m-win_amd64.whl", hash = "sha256:6e3874aa7e8babd37b40c4504e3a94cc2023696ced5a0500949f3347664ff8e2"}, 882 | {file = "pydantic-1.7.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:e682f6442ebe4e50cb5e1cfde7dda6766fb586631c3e5569f6aa1951fd1a76ef"}, 883 | {file = "pydantic-1.7.3-cp38-cp38-manylinux1_i686.whl", hash = "sha256:185e18134bec5ef43351149fe34fda4758e53d05bb8ea4d5928f0720997b79ef"}, 884 | {file = "pydantic-1.7.3-cp38-cp38-manylinux2014_i686.whl", hash = "sha256:f5b06f5099e163295b8ff5b1b71132ecf5866cc6e7f586d78d7d3fd6e8084608"}, 885 | {file = "pydantic-1.7.3-cp38-cp38-manylinux2014_x86_64.whl", hash = "sha256:24ca47365be2a5a3cc3f4a26dcc755bcdc9f0036f55dcedbd55663662ba145ec"}, 886 | {file = "pydantic-1.7.3-cp38-cp38-win_amd64.whl", hash = "sha256:d1fe3f0df8ac0f3a9792666c69a7cd70530f329036426d06b4f899c025aca74e"}, 887 | {file = "pydantic-1.7.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:f6864844b039805add62ebe8a8c676286340ba0c6d043ae5dea24114b82a319e"}, 888 | {file = "pydantic-1.7.3-cp39-cp39-manylinux1_i686.whl", hash = "sha256:ecb54491f98544c12c66ff3d15e701612fc388161fd455242447083350904730"}, 889 | {file = "pydantic-1.7.3-cp39-cp39-manylinux2014_i686.whl", hash = "sha256:ffd180ebd5dd2a9ac0da4e8b995c9c99e7c74c31f985ba090ee01d681b1c4b95"}, 890 | {file = "pydantic-1.7.3-cp39-cp39-manylinux2014_x86_64.whl", hash = "sha256:8d72e814c7821125b16f1553124d12faba88e85405b0864328899aceaad7282b"}, 891 | {file = "pydantic-1.7.3-cp39-cp39-win_amd64.whl", hash = "sha256:475f2fa134cf272d6631072554f845d0630907fce053926ff634cc6bc45bf1af"}, 892 | {file = "pydantic-1.7.3-py3-none-any.whl", hash = "sha256:38be427ea01a78206bcaf9a56f835784afcba9e5b88fbdce33bbbfbcd7841229"}, 893 | {file = "pydantic-1.7.3.tar.gz", hash = "sha256:213125b7e9e64713d16d988d10997dabc6a1f73f3991e1ff8e35ebb1409c7dc9"}, 894 | ] 895 | pyee = [ 896 | {file = "pyee-7.0.1-py2.py3-none-any.whl", hash = "sha256:705806bb09bc4b17a729d9a3d19a4f4e765abd010eb18e032d72f76452d07552"}, 897 | {file = "pyee-7.0.1.tar.gz", hash = "sha256:b0e5b89b17c8bd52a3c6517a545187907a8c69ce90169d29ebd8d2d5d7e4bc7d"}, 898 | ] 899 | pyflakes = [ 900 | {file = "pyflakes-2.1.1-py2.py3-none-any.whl", hash = "sha256:17dbeb2e3f4d772725c777fabc446d5634d1038f234e77343108ce445ea69ce0"}, 901 | {file = "pyflakes-2.1.1.tar.gz", hash = "sha256:d976835886f8c5b31d47970ed689944a0262b5f3afa00a5a7b4dc81e5449f8a2"}, 902 | ] 903 | pygments = [ 904 | {file = "Pygments-2.8.0-py3-none-any.whl", hash = "sha256:b21b072d0ccdf29297a82a2363359d99623597b8a265b8081760e4d0f7153c88"}, 905 | {file = "Pygments-2.8.0.tar.gz", hash = "sha256:37a13ba168a02ac54cc5891a42b1caec333e59b66addb7fa633ea8a6d73445c0"}, 906 | ] 907 | pyicu = [ 908 | {file = "PyICU-2.6.tar.gz", hash = "sha256:a9a5bf6833360f8f69e9375b91c1a7dd6e0c9157a42aee5bb7d6891804d96371"}, 909 | ] 910 | pyinstaller = [ 911 | {file = "PyInstaller-3.6.tar.gz", hash = "sha256:3730fa80d088f8bb7084d32480eb87cbb4ddb64123363763cf8f2a1378c1c4b7"}, 912 | ] 913 | pyparsing = [ 914 | {file = "pyparsing-2.4.6-py2.py3-none-any.whl", hash = "sha256:c342dccb5250c08d45fd6f8b4a559613ca603b57498511740e65cd11a2e7dcec"}, 915 | {file = "pyparsing-2.4.6.tar.gz", hash = "sha256:4c830582a84fb022400b85429791bc551f1f4871c33f23e44f353119e92f969f"}, 916 | ] 917 | pyperclip = [ 918 | {file = "pyperclip-1.7.0.tar.gz", hash = "sha256:979325468ccf682104d5dcaf753f869868100631301d3e72f47babdea5700d1c"}, 919 | ] 920 | pyppeteer2 = [ 921 | {file = "pyppeteer2-0.2.2-py3-none-any.whl", hash = "sha256:a469e08b67d6bd87c29d7b5e401d7843213e2a535b3ffbe39a00635535662a46"}, 922 | {file = "pyppeteer2-0.2.2.tar.gz", hash = "sha256:61c8aa4d6ae8d751bf9b6dfbe5b38710ce82b4bde2e47f1f34811fcf61c9290f"}, 923 | ] 924 | pyquery = [ 925 | {file = "pyquery-1.4.1-py2.py3-none-any.whl", hash = "sha256:710eac327b87f15f74a95c3378c6ba62ef6fcfb0a6d009a7d33349c9f7e65835"}, 926 | {file = "pyquery-1.4.1.tar.gz", hash = "sha256:8fcf77c72e3d602ce10a0bd4e65f57f0945c18e15627e49130c27172d4939d98"}, 927 | ] 928 | pytest = [ 929 | {file = "pytest-5.4.1-py3-none-any.whl", hash = "sha256:0e5b30f5cb04e887b91b1ee519fa3d89049595f428c1db76e73bd7f17b09b172"}, 930 | {file = "pytest-5.4.1.tar.gz", hash = "sha256:84dde37075b8805f3d1f392cc47e38a0e59518fb46a431cfdaf7cf1ce805f970"}, 931 | ] 932 | pytest-asyncio = [ 933 | {file = "pytest-asyncio-0.14.0.tar.gz", hash = "sha256:9882c0c6b24429449f5f969a5158b528f39bde47dc32e85b9f0403965017e700"}, 934 | {file = "pytest_asyncio-0.14.0-py3-none-any.whl", hash = "sha256:2eae1e34f6c68fc0a9dc12d4bea190483843ff4708d24277c41568d6b6044f1d"}, 935 | ] 936 | pytest-cov = [ 937 | {file = "pytest-cov-2.8.1.tar.gz", hash = "sha256:cc6742d8bac45070217169f5f72ceee1e0e55b0221f54bcf24845972d3a47f2b"}, 938 | {file = "pytest_cov-2.8.1-py2.py3-none-any.whl", hash = "sha256:cdbdef4f870408ebdbfeb44e63e07eb18bb4619fae852f6e760645fa36172626"}, 939 | ] 940 | python-dotenv = [ 941 | {file = "python-dotenv-0.15.0.tar.gz", hash = "sha256:587825ed60b1711daea4832cf37524dfd404325b7db5e25ebe88c495c9f807a0"}, 942 | {file = "python_dotenv-0.15.0-py2.py3-none-any.whl", hash = "sha256:0c8d1b80d1a1e91717ea7d526178e3882732420b03f08afea0406db6402e220e"}, 943 | ] 944 | six = [ 945 | {file = "six-1.14.0-py2.py3-none-any.whl", hash = "sha256:8f3cd2e254d8f793e7f3d6d9df77b92252b52637291d0f0da013c76ea2724b6c"}, 946 | {file = "six-1.14.0.tar.gz", hash = "sha256:236bdbdce46e6e6a3d61a337c0f8b763ca1e8717c03b369e87a7ec7ce1319c0a"}, 947 | ] 948 | tqdm = [ 949 | {file = "tqdm-4.43.0-py2.py3-none-any.whl", hash = "sha256:0d8b5afb66e23d80433102e9bd8b5c8b65d34c2a2255b2de58d97bd2ea8170fd"}, 950 | {file = "tqdm-4.43.0.tar.gz", hash = "sha256:f35fb121bafa030bd94e74fcfd44f3c2830039a2ddef7fc87ef1c2d205237b24"}, 951 | ] 952 | traitlets = [ 953 | {file = "traitlets-5.0.5-py3-none-any.whl", hash = "sha256:69ff3f9d5351f31a7ad80443c2674b7099df13cc41fc5fa6e2f6d3b0330b0426"}, 954 | {file = "traitlets-5.0.5.tar.gz", hash = "sha256:178f4ce988f69189f7e523337a3e11d91c786ded9360174a3d9ca83e79bc5396"}, 955 | ] 956 | urllib3 = [ 957 | {file = "urllib3-1.25.9-py2.py3-none-any.whl", hash = "sha256:88206b0eb87e6d677d424843ac5209e3fb9d0190d0ee169599165ec25e9d9115"}, 958 | {file = "urllib3-1.25.9.tar.gz", hash = "sha256:3018294ebefce6572a474f0604c2021e33b3fd8006ecd11d62107a5d2a963527"}, 959 | ] 960 | wcwidth = [ 961 | {file = "wcwidth-0.1.8-py2.py3-none-any.whl", hash = "sha256:8fd29383f539be45b20bd4df0dc29c20ba48654a41e661925e612311e9f3c603"}, 962 | {file = "wcwidth-0.1.8.tar.gz", hash = "sha256:f28b3e8a6483e5d49e7f8949ac1a78314e740333ae305b4ba5defd3e74fb37a8"}, 963 | ] 964 | websockets = [ 965 | {file = "websockets-8.1-cp36-cp36m-macosx_10_6_intel.whl", hash = "sha256:3762791ab8b38948f0c4d281c8b2ddfa99b7e510e46bd8dfa942a5fff621068c"}, 966 | {file = "websockets-8.1-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:3db87421956f1b0779a7564915875ba774295cc86e81bc671631379371af1170"}, 967 | {file = "websockets-8.1-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:4f9f7d28ce1d8f1295717c2c25b732c2bc0645db3215cf757551c392177d7cb8"}, 968 | {file = "websockets-8.1-cp36-cp36m-manylinux2010_i686.whl", hash = "sha256:295359a2cc78736737dd88c343cd0747546b2174b5e1adc223824bcaf3e164cb"}, 969 | {file = "websockets-8.1-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:1d3f1bf059d04a4e0eb4985a887d49195e15ebabc42364f4eb564b1d065793f5"}, 970 | {file = "websockets-8.1-cp36-cp36m-win32.whl", hash = "sha256:2db62a9142e88535038a6bcfea70ef9447696ea77891aebb730a333a51ed559a"}, 971 | {file = "websockets-8.1-cp36-cp36m-win_amd64.whl", hash = "sha256:0e4fb4de42701340bd2353bb2eee45314651caa6ccee80dbd5f5d5978888fed5"}, 972 | {file = "websockets-8.1-cp37-cp37m-macosx_10_6_intel.whl", hash = "sha256:9b248ba3dd8a03b1a10b19efe7d4f7fa41d158fdaa95e2cf65af5a7b95a4f989"}, 973 | {file = "websockets-8.1-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:ce85b06a10fc65e6143518b96d3dca27b081a740bae261c2fb20375801a9d56d"}, 974 | {file = "websockets-8.1-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:965889d9f0e2a75edd81a07592d0ced54daa5b0785f57dc429c378edbcffe779"}, 975 | {file = "websockets-8.1-cp37-cp37m-manylinux2010_i686.whl", hash = "sha256:751a556205d8245ff94aeef23546a1113b1dd4f6e4d102ded66c39b99c2ce6c8"}, 976 | {file = "websockets-8.1-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:3ef56fcc7b1ff90de46ccd5a687bbd13a3180132268c4254fc0fa44ecf4fc422"}, 977 | {file = "websockets-8.1-cp37-cp37m-win32.whl", hash = "sha256:7ff46d441db78241f4c6c27b3868c9ae71473fe03341340d2dfdbe8d79310acc"}, 978 | {file = "websockets-8.1-cp37-cp37m-win_amd64.whl", hash = "sha256:20891f0dddade307ffddf593c733a3fdb6b83e6f9eef85908113e628fa5a8308"}, 979 | {file = "websockets-8.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:c1ec8db4fac31850286b7cd3b9c0e1b944204668b8eb721674916d4e28744092"}, 980 | {file = "websockets-8.1-cp38-cp38-manylinux1_i686.whl", hash = "sha256:5c01fd846263a75bc8a2b9542606927cfad57e7282965d96b93c387622487485"}, 981 | {file = "websockets-8.1-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:9bef37ee224e104a413f0780e29adb3e514a5b698aabe0d969a6ba426b8435d1"}, 982 | {file = "websockets-8.1-cp38-cp38-manylinux2010_i686.whl", hash = "sha256:d705f8aeecdf3262379644e4b55107a3b55860eb812b673b28d0fbc347a60c55"}, 983 | {file = "websockets-8.1-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:c8a116feafdb1f84607cb3b14aa1418424ae71fee131642fc568d21423b51824"}, 984 | {file = "websockets-8.1-cp38-cp38-win32.whl", hash = "sha256:e898a0863421650f0bebac8ba40840fc02258ef4714cb7e1fd76b6a6354bda36"}, 985 | {file = "websockets-8.1-cp38-cp38-win_amd64.whl", hash = "sha256:f8a7bff6e8664afc4e6c28b983845c5bc14965030e3fb98789734d416af77c4b"}, 986 | {file = "websockets-8.1.tar.gz", hash = "sha256:5c65d2da8c6bce0fca2528f69f44b2f977e06954c8512a952222cea50dad430f"}, 987 | ] 988 | zipp = [ 989 | {file = "zipp-3.1.0-py3-none-any.whl", hash = "sha256:aa36550ff0c0b7ef7fa639055d797116ee891440eac1a56f378e2d3179e0320b"}, 990 | {file = "zipp-3.1.0.tar.gz", hash = "sha256:c599e4d75c98f6798c509911d08a22e6c021d074469042177c8c86fb92eefd96"}, 991 | ] 992 | -------------------------------------------------------------------------------- /pyproject - Copy.toml: -------------------------------------------------------------------------------- 1 | [tool.poetry] 2 | name = "deepl-tr-async" 3 | version = "0.0.2" 4 | description = "deepl translate for free, based no pyppeteer" 5 | authors = ["ffreemt"] 6 | license = "MIT" 7 | readme = "README.md" 8 | homepage = "https://github.com/ffremt/deepl-tr-async" 9 | repository = "https://github.com/ffremt/deepl-tr-async" 10 | keywords = ["mt", "language", "learning"] 11 | classifiers = [ 12 | "Environment :: Console", 13 | "Framework :: MT", 14 | "Operating System :: OS Independent", 15 | "Topic :: Software Development :: Machine Translation", 16 | "Topic :: Software Development :: Libraries :: Python Modules", 17 | "Topic :: Software Development :: Tools", 18 | ] 19 | [tool.poetry.dependencies] 20 | python = "^3.6" 21 | pyperclip = "^1.7.0" 22 | pyppeteer = "^0.0.25" 23 | pyquery = "^1.4.1" 24 | python-dotenv = "^0.12.0" 25 | logzero = "^1.5.0" 26 | environs = "^7.3.1" 27 | linetimer = "^0.1.4" 28 | absl-py = "^0.9.0" 29 | polyglot = "^16.7.4" 30 | fuzzywuzzy = "^0.18.0" 31 | pyinstaller = "^3.6" 32 | tqdm = "^4.43.0" 33 | flake8 = "^3.7.9" 34 | pyicu = "^2.4.3" 35 | pycld2 = "^0.41" 36 | 37 | [tool.poetry.dev-dependencies] 38 | pytest = "^5.2" 39 | 40 | # New scripts 41 | [tool.poetry.scripts] 42 | deepl-tr = "deepl_tr_async.__main__:main" 43 | # my-script = "xtl_read_assistant.log_revision:start" 44 | # hello = "xtl_read_assistant.read_assistant:hello" 45 | 46 | # [build-system] 47 | # requires = ["poetry>=0.12"] 48 | # build-backend = "poetry.masonry.api" 49 | 50 | [tool.intreehooks] 51 | build-backend = "poetry.masonry.api" 52 | [build-system] 53 | requires = ["intreehooks"] 54 | build-backend = "intreehooks:loader" 55 | 56 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [tool.poetry] 2 | name = "deepl-tr-async" 3 | version = "0.0.5" 4 | description = "deepl translate for free, based on pyppeteer" 5 | authors = ["ffreemt"] 6 | license = "MIT" 7 | readme = "README.md" 8 | homepage = "https://github.com/ffreemt/deepl-tr-async" 9 | repository = "https://github.com/ffreemt/deepl-tr-async" 10 | keywords = ["mt", "language", "learning"] 11 | classifiers = [ 12 | "Environment :: Console", 13 | "Operating System :: OS Independent", 14 | "Topic :: Software Development :: Libraries :: Python Modules", 15 | ] 16 | 17 | [tool.poetry.dependencies] 18 | python = "^3.7" 19 | pyperclip = "^1.7.0" 20 | pyquery = "^1.4.1" 21 | logzero = "^1.5.0" 22 | environs = "^7.3.1" 23 | linetimer = "^0.1.4" 24 | absl-py = "^0.9.0" 25 | polyglot = "^16.7.4" 26 | fuzzywuzzy = "^0.18.0" 27 | pyinstaller = "^3.6" 28 | tqdm = "^4.43.0" 29 | flake8 = "^3.7.9" 30 | pyicu = "^2.6" 31 | pycld2 = "^0.41" 32 | pytest-cov = "^2.8.1" 33 | pyppeteer2 = "^0.2.2" 34 | get-ppbrowser = "^0.1.1" 35 | ipython = "^7.20.0" 36 | 37 | [tool.poetry.dev-dependencies] 38 | pytest = "^5.2" 39 | 40 | # New scripts 41 | [tool.poetry.scripts] 42 | deepl-tr = "deepl_tr_async.__main__:main" 43 | # my-script = "xtl_read_assistant.log_revision:start" 44 | # hello = "xtl_read_assistant.read_assistant:hello" 45 | 46 | [build-system] 47 | requires = ["poetry>=0.12"] 48 | build-backend = "poetry.masonry.api" 49 | 50 | # [tool.intreehooks] 51 | # build-backend = "poetry.masonry.api" 52 | # [build-system] 53 | # requires = ["intreehooks"] 54 | # build-backend = "intreehooks:loader" 55 | 56 | -------------------------------------------------------------------------------- /pyrightconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "typingsPath": "src/typestubs", 3 | "venvPath": "/Python/Python36", 4 | 5 | "reportTypeshedErrors": false, 6 | "reportMissingImports": true, 7 | "reportMissingTypeStubs": false, 8 | 9 | "pythonVersion": "3.6", 10 | "pythonPlatform": "Windows" 11 | } -------------------------------------------------------------------------------- /pytest.ini: -------------------------------------------------------------------------------- 1 | # content of pytest.ini 2 | [pytest] 3 | # addopts = --doctest-modules 4 | log_cli = true -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | absl-py==0.9.0 2 | altgraph==0.17 3 | appdirs==1.4.3 4 | atomicwrites==1.3.0; sys_platform == "win32" 5 | attrs==19.3.0 6 | colorama==0.4.3; sys_platform == "win32" 7 | coverage==5.0.4 8 | cssselect==1.1.0 9 | dis3==0.1.3 10 | entrypoints==0.3 11 | environs==7.3.1 12 | flake8==3.7.9 13 | fuzzywuzzy==0.18.0 14 | importlib-metadata==1.5.0; python_version < "3.8" 15 | linetimer==0.1.4 16 | logzero==1.5.0 17 | lxml==4.5.0 18 | marshmallow==3.5.1 19 | mccabe==0.6.1 20 | more-itertools==8.2.0 21 | packaging==20.3 22 | pluggy==0.13.1 23 | polyglot==16.7.4 24 | py==1.8.1 25 | pycld2==0.41 26 | pycodestyle==2.5.0 27 | pyee==7.0.1 28 | pyflakes==2.1.1 29 | pyicu==2.4.3 30 | pyinstaller==3.6 31 | pyparsing==2.4.6 32 | pyperclip==1.7.0 33 | pyppeteer2==0.2.2 34 | pyquery==1.4.1 35 | pytest==5.4.1 36 | pytest-cov==2.8.1 37 | python-dotenv==0.12.0 38 | six==1.14.0 39 | tqdm==4.43.0 40 | urllib3==1.25.9 41 | wcwidth==0.1.8 42 | websockets==8.1 43 | zipp==3.1.0; python_version < "3.8" 44 | -------------------------------------------------------------------------------- /tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ffreemt/deepl-tr-async/d71e5f87c922f99bb40312e286accdf3c76013cb/tests/__init__.py -------------------------------------------------------------------------------- /tests/test_deepl_tr_async.py: -------------------------------------------------------------------------------- 1 | """Test deelp.""" 2 | import asyncio 3 | import pytest 4 | from logzero import logger 5 | 6 | from deepl_tr_async import __version__ 7 | 8 | # from deepl_tr_async.deepl_tr_async import deepl_tr_async 9 | from deepl_tr_async import deepl_tr_async 10 | 11 | LOOP = asyncio.get_event_loop() 12 | 13 | 14 | def test_version(): 15 | logger.info("\n\t version: %s", __version__) 16 | assert __version__[:4] == "0.0." 17 | 18 | 19 | # @pytest.mark.asyncio 20 | # --show-capture: invalid choice: 'yes' (choose from 'no', 'stdout', 'stderr', 'log', 'all') 21 | # -s show normal print/logger.info/debug output 22 | def test_deepl_en_zh(caplog): 23 | """ test_deepl_en_zh""" 24 | 25 | text = "test this and that" 26 | 27 | text1 = "A dedicate website will go live in January and the Task Force has planned a two to three-month sprint to kick start its work." 28 | exp1 = "专门网站将于1月上线,工作队计划用两到三个月的时间冲刺,以启动其工作。" 29 | 30 | res = LOOP.run_until_complete(deepl_tr_async(text, to_lang="zh")) 31 | # res = await deepl_tr_async(text) 32 | # with caplog.at_level(20): 33 | with caplog.at_level(10): 34 | # logger.info("test_deepl_en_zh res: %s", res) 35 | logger.info("test_deepl_en_zh res: %s", res) 36 | logger.info("caplog.text: %s", caplog.text) 37 | # pytest -s 38 | 39 | _ = ["测验", "试探", "检验"] 40 | assert any(map(lambda elm: elm in res, _)) 41 | -------------------------------------------------------------------------------- /tests/test_detect_lang.py: -------------------------------------------------------------------------------- 1 | """ test detect_lang""" 2 | from deepl_tr_async.detect_lang import detect_lang 3 | 4 | 5 | def test_en(): 6 | """ 7 | test en 8 | """ 9 | 10 | text = "This is an English test " 11 | text = """There is some concern that unifying the Han characters may lead to confusion because they are sometimes used differently by the various East Asian languages. Computationally, Han character unification presents no more difficulty than employing a single Latin character set that is used to write languages as different as English and French. Programmers do not expect the characters “c”, “h”, “a”, and “t” alone to tell us whether chat is a French word for cat or an English word meaning “informal talk.” Likewise, we depend on context to identify the American hood (of a car) with the British bonnet. Few computer users are confused by the fact that ASCII can also be used to represent such words""" # NOQA 12 | # eq_('english', detect_lang(text)) 13 | assert detect_lang(text) == "en" 14 | assert detect_lang(text, 1) == "english" 15 | 16 | text = """Forum libre! Discutez de n'importe quoi en français. 17 | Chat about anything you'd like, en français. 18 | """ 19 | text = """Forum libre! Discutez de n'importe quoi en français. 20 | en français. 21 | """ 22 | # eq_('french', detect_lang(text)) 23 | assert "fr" == detect_lang(text) 24 | assert "french" == detect_lang(text, 1) 25 | 26 | text = "Ogni individuo ha diritto all'istruzione. L'istruzione deve essere gratuita almeno per quanto riguarda le classi elementari e fondamentali. L'istruzione elementare deve essere obbligatoria. L'istruzione tecnica e professionale deve essere messa alla portata di tutti e l'istruzione superiore deve essere egualmente accessibile a tutti sulla base del merito." # NOQA 27 | # eq_('italian', detect_lang(text)) 28 | assert "it" == detect_lang(text) 29 | assert "italian" == detect_lang(text, 1) 30 | 31 | text = "auf deutsch" 32 | # eq_('german', detect_lang(text)) 33 | assert "de" == detect_lang(text) 34 | assert "german" == detect_lang(text, 1) 35 | 36 | 37 | def test_zh(): 38 | """ 39 | test zh 40 | """ 41 | text = """人类学家说,只有一个生物常数:女的在所有的社会角色,包括轴承、儿童和初级保健护理。否则,几乎任何事情–只要去找女人的一种方式和其他人。 42 | 【吐槽】sky (2931712793) 19:40:17""" 43 | 44 | # eq_('chinese', detect_lang(text)) 45 | assert "zh" == detect_lang(text) 46 | assert "chinese" == detect_lang(text, 1) 47 | 48 | 49 | def test_pt(): 50 | """ 51 | test portuguese 52 | """ 53 | text = """ O Brasil é um país que sempre foi referido por outras nações por seu tamanho ou por sua população. Mas em discussões entre os cientistas, jornalistas, economistas, e experientes internacionais, este país é muitas vezes caracterizado como um país subdesenvolvido.""" # NOQA 54 | 55 | # eq_('portuguese', detect_lang(text)) 56 | assert "pt" == detect_lang(text) 57 | assert "portuguese" == detect_lang(text, 1) 58 | 59 | 60 | def test_es(): 61 | """ 62 | test spanish 63 | """ 64 | text = """ Es un área definida de la superficie, ya sea de tierra, agua o hielo propuesto para la llegada, salida y movimiento en superficie de aeronaves de distintos tipos con llegadas y salidas nacionales e internacionales. """ # NOQA 65 | 66 | # eq_('spanish', detect_lang(text)) 67 | assert "es" == detect_lang(text) 68 | assert "spanish" == detect_lang(text, 1) 69 | -------------------------------------------------------------------------------- /tests/test_google_tr_async.py: -------------------------------------------------------------------------------- 1 | """ test deelp """ 2 | import asyncio 3 | 4 | # import pytest 5 | from logzero import logger 6 | import warnings 7 | 8 | from deepl_tr_async import __version__ 9 | 10 | # from deepl_tr_async.deepl_tr_async import deepl_tr_async 11 | # from deepl_tr_async import deepl_tr_async 12 | from deepl_tr_async.google_tr_async import google_tr_async 13 | 14 | warnings.filterwarnings("ignore", ".*pure-python.*") # regex 15 | 16 | LOOP = asyncio.get_event_loop() 17 | 18 | 19 | def test_version(): 20 | assert __version__[:4] == "0.0." 21 | 22 | 23 | # @pytest.mark.asyncio 24 | def test_google_en_zh(caplog): 25 | """ test_google_en_zh""" 26 | 27 | text = "test this and that" 28 | # res = await deepl_tr_async(text) 29 | # res = LOOP.run_until_complete(deepl_tr_async(text, to_lang="zh")) 30 | res = LOOP.run_until_complete(google_tr_async(text, to_lang="zh")) 31 | logger.info(" res: %s", res) 32 | 33 | with caplog.at_level(20): # caplog.text 34 | logger.debug("test_google_en_zh res: %s", res) 35 | assert all(map(lambda elm: elm in res, "测试个")) 36 | 37 | 38 | def test_google_en_de(): 39 | """ test_google_en_de""" 40 | 41 | text = "test this and that" 42 | # res = await deepl_tr_async(text) 43 | # res = LOOP.run_until_complete(deepl_tr_async(text, to_lang="de")) 44 | res = LOOP.run_until_complete(google_tr_async(text, to_lang="de")) 45 | logger.info(" res: %s", res) 46 | 47 | assert res.lower() == "teste dies und das" 48 | 49 | # pytest -s to show log in this file 50 | --------------------------------------------------------------------------------