├── .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 [](https://img.shields.io/static/v1?label=python+&message=3.7%2B&color=blue)[](https://github.com/ffreemt/deepl-tr-async/actions/workflows/build.yml)[](https://codecov.io/gh/ffreemt/deepl-tr-async)[](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 | 
40 | * Help 帮助:
41 |
42 | `deepl-tr -?`
43 |
44 | or
45 |
46 | `deepl-tr --helpfull`
47 |
48 | 
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 |
--------------------------------------------------------------------------------