41 | ```
42 |
43 | Create Caddyfile
44 |
45 | ```text
46 | $ touch Caddyfile
47 | ```
48 |
49 | Edit the Caddyfile as follows:
50 |
51 | ```text
52 | :8080 {
53 | proxy / localhost:5000 {
54 | transparent
55 | }
56 | }
57 | ```
58 |
59 | Next step:
60 |
61 | ```text
62 | $ caddy
63 | Activating privacy features... done.
64 | http://localhost:8080
65 | ```
66 |
67 | Well done! Everything is ok! You can get more information from `topics` or just
68 | go to write your own APIs!
69 |
70 | ## The result directory structure:
71 |
72 | ```text
73 | $ tree .
74 | .
75 | ├── app.py
76 | ├── Caddyfile
77 | ├── data.sqlite
78 | ├── items
79 | │ ├── __init__.py
80 | │ ├── pexels.py
81 | │ ├── pixabay.py
82 | │ └── __pycache__
83 | │ ├── __init__.cpython-36.pyc
84 | │ ├── pexels.cpython-36.pyc
85 | │ └── pixabay.cpython-36.pyc
86 | ├── __pycache__
87 | │ ├── app.cpython-36.pyc
88 | │ ├── settings.cpython-36.pyc
89 | │ └── wsgi.cpython-36.pyc
90 | ├── README.md
91 | ├── settings.py
92 | └── wsgi.py
93 |
94 | 3 directories, 15 files
95 | ```
--------------------------------------------------------------------------------
/examples/click/app.py:
--------------------------------------------------------------------------------
1 | from flask import Flask, render_template
2 |
3 | from toapi import Api
4 |
5 | app = Flask(__name__)
6 |
7 |
8 | @app.route("/")
9 | def index():
10 | html = Api().fetch("https://movie.douban.com/")
11 | return render_template("index.html", **{"html": html})
12 |
13 |
14 | app.run(debug=True)
15 |
--------------------------------------------------------------------------------
/examples/click/static/main.js:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/elliotgao2/toapi/6ae043cab28d16beb0be1bd9b1cd0fdc9c19baa6/examples/click/static/main.js
--------------------------------------------------------------------------------
/examples/click/templates/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Title
6 |
7 |
45 |
46 |
49 |
50 | {{ html |safe}}
51 |
52 |
53 |
54 |
74 |
75 |
--------------------------------------------------------------------------------
/examples/hackernews_page.py:
--------------------------------------------------------------------------------
1 | from flask import request
2 | from htmlparsing import Attr, Text
3 |
4 | from toapi import Api, Item
5 |
6 | api = Api(browser="/home/bug/桌面/geckodriver")
7 |
8 |
9 | @api.site("https://news.ycombinator.com")
10 | @api.list(".athing")
11 | @api.route("/posts?page={page}", "/news?p={page}")
12 | @api.route("/posts", "/news?p=1")
13 | class Post(Item):
14 | url = Attr(".storylink", "href")
15 | title = Text(".storylink")
16 |
17 |
18 | @api.site("https://news.ycombinator.com")
19 | @api.route("/posts?page={page}", "/news?p={page}")
20 | @api.route("/posts", "/news?p=1")
21 | class Page(Item):
22 | next_page = Attr(".morelink", "href")
23 |
24 | def clean_next_page(self, value):
25 | return api.convert_string(
26 | "/" + value,
27 | "/news?p={page}",
28 | request.host_url.strip("/") + "/posts?page={page}",
29 | )
30 |
31 |
32 | api.run(debug=True, host="0.0.0.0", port=5000)
33 |
--------------------------------------------------------------------------------
/mkdocs.yml:
--------------------------------------------------------------------------------
1 | site_name: Toapi
2 | site_url: http://www.toapi.org
3 | site_description: Every web site provides APIs.
4 | site_author: Jiuli Gao
5 |
6 | repo_url: https://github.com/gaojiuli/toapi/
7 | repo_name: 'gaojiuli/toapi'
8 |
9 | pages:
10 | - Toapi:
11 | - Introduce: index.md
12 | - Installation: about/installation.md
13 | - Release Notes: about/release-notes.md
14 | - Contributing: about/contributing.md
15 | - License: about/license.md
16 | - Tutorials:
17 | - Introducing Aim: tutorials/introducing.md
18 | - Step 0 Creating New Project: tutorials/step0-creating-new-project.md
19 | - Step 1 Global Settings: tutorials/step1-global-settings.md
20 | - Step 2 Using Redis: tutorials/step2-redis.md
21 | - Step 3 Using Sqlite: tutorials/step3-sqlite3.md
22 | - Step 4 Defining Items: tutorials/step4-defining-items.md
23 | - Step 5 Deploy: tutorials/step5-deploy.md
24 | - Topics:
25 | - Api: topics/api.md
26 | - Item: topics/item.md
27 | - Selector: topics/selector.md
28 | - Settings: topics/settings.md
29 | - Cache: topics/cache.md
30 | - Storage: topics/storage.md
31 | - Articles:
32 | - Toapi released: articles/release.md
33 |
34 | markdown_extensions:
35 | - toc:
36 | permalink:
37 | - admonition
38 | - def_list
39 | - codehilite
40 |
41 | copyright: Copyright © 2017 Jiuli Gao.
42 |
43 | theme:
44 | name: 'material'
45 | palette:
46 | primary: 'blue'
47 | accent: 'blue'
48 | font:
49 | text: 'Ubuntu'
50 | code: 'Ubuntu Mono'
51 | logo: './logo.png'
52 | social:
53 | - type: 'github'
54 | link: 'https://github.com/gaojiuli'
55 | plugins:
56 | - search
--------------------------------------------------------------------------------
/poetry.lock:
--------------------------------------------------------------------------------
1 | [[package]]
2 | name = "appdirs"
3 | version = "1.4.4"
4 | description = "A small Python module for determining appropriate platform-specific dirs, e.g. a \"user data dir\"."
5 | category = "dev"
6 | optional = false
7 | python-versions = "*"
8 |
9 | [package.source]
10 | type = "legacy"
11 | url = "https://pypi.doubanio.com/simple"
12 | reference = "douban"
13 |
14 | [[package]]
15 | name = "atomicwrites"
16 | version = "1.4.0"
17 | description = "Atomic file writes."
18 | category = "dev"
19 | optional = false
20 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
21 |
22 | [package.source]
23 | type = "legacy"
24 | url = "https://pypi.doubanio.com/simple"
25 | reference = "douban"
26 |
27 | [[package]]
28 | name = "attrs"
29 | version = "21.2.0"
30 | description = "Classes Without Boilerplate"
31 | category = "dev"
32 | optional = false
33 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
34 |
35 | [package.extras]
36 | dev = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "mypy", "pytest-mypy-plugins", "zope.interface", "furo", "sphinx", "sphinx-notfound-page", "pre-commit"]
37 | docs = ["furo", "sphinx", "zope.interface", "sphinx-notfound-page"]
38 | tests = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "mypy", "pytest-mypy-plugins", "zope.interface"]
39 | tests_no_zope = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "mypy", "pytest-mypy-plugins"]
40 |
41 | [package.source]
42 | type = "legacy"
43 | url = "https://pypi.doubanio.com/simple"
44 | reference = "douban"
45 |
46 | [[package]]
47 | name = "beautifulsoup4"
48 | version = "4.9.3"
49 | description = "Screen-scraping library"
50 | category = "dev"
51 | optional = false
52 | python-versions = "*"
53 |
54 | [package.dependencies]
55 | soupsieve = {version = ">1.2", markers = "python_version >= \"3.0\""}
56 |
57 | [package.extras]
58 | html5lib = ["html5lib"]
59 | lxml = ["lxml"]
60 |
61 | [package.source]
62 | type = "legacy"
63 | url = "https://pypi.doubanio.com/simple"
64 | reference = "douban"
65 |
66 | [[package]]
67 | name = "cchardet"
68 | version = "2.1.7"
69 | description = "cChardet is high speed universal character encoding detector."
70 | category = "main"
71 | optional = false
72 | python-versions = "*"
73 |
74 | [package.source]
75 | type = "legacy"
76 | url = "https://pypi.doubanio.com/simple"
77 | reference = "douban"
78 |
79 | [[package]]
80 | name = "certifi"
81 | version = "2021.5.30"
82 | description = "Python package for providing Mozilla's CA Bundle."
83 | category = "main"
84 | optional = false
85 | python-versions = "*"
86 |
87 | [package.source]
88 | type = "legacy"
89 | url = "https://pypi.doubanio.com/simple"
90 | reference = "douban"
91 |
92 | [[package]]
93 | name = "cfgv"
94 | version = "3.3.0"
95 | description = "Validate configuration and produce human readable error messages."
96 | category = "dev"
97 | optional = false
98 | python-versions = ">=3.6.1"
99 |
100 | [package.source]
101 | type = "legacy"
102 | url = "https://pypi.doubanio.com/simple"
103 | reference = "douban"
104 |
105 | [[package]]
106 | name = "chardet"
107 | version = "4.0.0"
108 | description = "Universal encoding detector for Python 2 and 3"
109 | category = "main"
110 | optional = false
111 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
112 |
113 | [package.source]
114 | type = "legacy"
115 | url = "https://pypi.doubanio.com/simple"
116 | reference = "douban"
117 |
118 | [[package]]
119 | name = "click"
120 | version = "8.0.1"
121 | description = "Composable command line interface toolkit"
122 | category = "main"
123 | optional = false
124 | python-versions = ">=3.6"
125 |
126 | [package.dependencies]
127 | colorama = {version = "*", markers = "platform_system == \"Windows\""}
128 |
129 | [package.source]
130 | type = "legacy"
131 | url = "https://pypi.doubanio.com/simple"
132 | reference = "douban"
133 |
134 | [[package]]
135 | name = "codecov"
136 | version = "2.1.11"
137 | description = "Hosted coverage reports for GitHub, Bitbucket and Gitlab"
138 | category = "dev"
139 | optional = false
140 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
141 |
142 | [package.dependencies]
143 | coverage = "*"
144 | requests = ">=2.7.9"
145 |
146 | [package.source]
147 | type = "legacy"
148 | url = "https://pypi.doubanio.com/simple"
149 | reference = "douban"
150 |
151 | [[package]]
152 | name = "colorama"
153 | version = "0.4.4"
154 | description = "Cross-platform colored terminal text."
155 | category = "main"
156 | optional = false
157 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
158 |
159 | [package.source]
160 | type = "legacy"
161 | url = "https://pypi.doubanio.com/simple"
162 | reference = "douban"
163 |
164 | [[package]]
165 | name = "coverage"
166 | version = "5.5"
167 | description = "Code coverage measurement for Python"
168 | category = "dev"
169 | optional = false
170 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, <4"
171 |
172 | [package.extras]
173 | toml = ["toml"]
174 |
175 | [package.source]
176 | type = "legacy"
177 | url = "https://pypi.doubanio.com/simple"
178 | reference = "douban"
179 |
180 | [[package]]
181 | name = "cssselect"
182 | version = "1.1.0"
183 | description = "cssselect parses CSS3 Selectors and translates them to XPath 1.0"
184 | category = "main"
185 | optional = false
186 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
187 |
188 | [package.source]
189 | type = "legacy"
190 | url = "https://pypi.doubanio.com/simple"
191 | reference = "douban"
192 |
193 | [[package]]
194 | name = "distlib"
195 | version = "0.3.2"
196 | description = "Distribution utilities"
197 | category = "dev"
198 | optional = false
199 | python-versions = "*"
200 |
201 | [package.source]
202 | type = "legacy"
203 | url = "https://pypi.doubanio.com/simple"
204 | reference = "douban"
205 |
206 | [[package]]
207 | name = "execnet"
208 | version = "1.9.0"
209 | description = "execnet: rapid multi-Python deployment"
210 | category = "dev"
211 | optional = false
212 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
213 |
214 | [package.extras]
215 | testing = ["pre-commit"]
216 |
217 | [package.source]
218 | type = "legacy"
219 | url = "https://pypi.doubanio.com/simple"
220 | reference = "douban"
221 |
222 | [[package]]
223 | name = "filelock"
224 | version = "3.0.12"
225 | description = "A platform independent file lock."
226 | category = "dev"
227 | optional = false
228 | python-versions = "*"
229 |
230 | [package.source]
231 | type = "legacy"
232 | url = "https://pypi.doubanio.com/simple"
233 | reference = "douban"
234 |
235 | [[package]]
236 | name = "flask"
237 | version = "2.0.1"
238 | description = "A simple framework for building complex web applications."
239 | category = "main"
240 | optional = false
241 | python-versions = ">=3.6"
242 |
243 | [package.dependencies]
244 | click = ">=7.1.2"
245 | itsdangerous = ">=2.0"
246 | Jinja2 = ">=3.0"
247 | Werkzeug = ">=2.0"
248 |
249 | [package.extras]
250 | async = ["asgiref (>=3.2)"]
251 | dotenv = ["python-dotenv"]
252 |
253 | [package.source]
254 | type = "legacy"
255 | url = "https://pypi.doubanio.com/simple"
256 | reference = "douban"
257 |
258 | [[package]]
259 | name = "ghp-import"
260 | version = "2.0.1"
261 | description = "Copy your docs directly to the gh-pages branch."
262 | category = "dev"
263 | optional = false
264 | python-versions = "*"
265 |
266 | [package.dependencies]
267 | python-dateutil = ">=2.8.1"
268 |
269 | [package.extras]
270 | dev = ["twine", "markdown", "flake8"]
271 |
272 | [package.source]
273 | type = "legacy"
274 | url = "https://pypi.doubanio.com/simple"
275 | reference = "douban"
276 |
277 | [[package]]
278 | name = "html2text"
279 | version = "2020.1.16"
280 | description = "Turn HTML into equivalent Markdown-structured text."
281 | category = "main"
282 | optional = false
283 | python-versions = ">=3.5"
284 |
285 | [package.source]
286 | type = "legacy"
287 | url = "https://pypi.doubanio.com/simple"
288 | reference = "douban"
289 |
290 | [[package]]
291 | name = "htmlfetcher"
292 | version = "0.0.6"
293 | description = "No pain HTML fetching library."
294 | category = "main"
295 | optional = false
296 | python-versions = "*"
297 |
298 | [package.dependencies]
299 | selenium = "*"
300 |
301 | [package.source]
302 | type = "legacy"
303 | url = "https://pypi.doubanio.com/simple"
304 | reference = "douban"
305 |
306 | [[package]]
307 | name = "htmlparsing"
308 | version = "0.1.5"
309 | description = "Pure HTML parsing library."
310 | category = "main"
311 | optional = false
312 | python-versions = "*"
313 |
314 | [package.dependencies]
315 | html2text = "*"
316 | lxml = "*"
317 | parse = "*"
318 |
319 | [package.source]
320 | type = "legacy"
321 | url = "https://pypi.doubanio.com/simple"
322 | reference = "douban"
323 |
324 | [[package]]
325 | name = "identify"
326 | version = "2.2.10"
327 | description = "File identification library for Python"
328 | category = "dev"
329 | optional = false
330 | python-versions = ">=3.6.1"
331 |
332 | [package.extras]
333 | license = ["editdistance-s"]
334 |
335 | [package.source]
336 | type = "legacy"
337 | url = "https://pypi.doubanio.com/simple"
338 | reference = "douban"
339 |
340 | [[package]]
341 | name = "idna"
342 | version = "2.10"
343 | description = "Internationalized Domain Names in Applications (IDNA)"
344 | category = "main"
345 | optional = false
346 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
347 |
348 | [package.source]
349 | type = "legacy"
350 | url = "https://pypi.doubanio.com/simple"
351 | reference = "douban"
352 |
353 | [[package]]
354 | name = "importlib-metadata"
355 | version = "4.6.0"
356 | description = "Read metadata from Python packages"
357 | category = "dev"
358 | optional = false
359 | python-versions = ">=3.6"
360 |
361 | [package.dependencies]
362 | zipp = ">=0.5"
363 |
364 | [package.extras]
365 | docs = ["sphinx", "jaraco.packaging (>=8.2)", "rst.linker (>=1.9)"]
366 | perf = ["ipython"]
367 | testing = ["pytest (>=4.6)", "pytest-checkdocs (>=2.4)", "pytest-flake8", "pytest-cov", "pytest-enabler (>=1.0.1)", "packaging", "pep517", "pyfakefs", "flufl.flake8", "pytest-perf (>=0.9.2)", "pytest-black (>=0.3.7)", "pytest-mypy", "importlib-resources (>=1.3)"]
368 |
369 | [package.source]
370 | type = "legacy"
371 | url = "https://pypi.doubanio.com/simple"
372 | reference = "douban"
373 |
374 | [[package]]
375 | name = "iniconfig"
376 | version = "1.1.1"
377 | description = "iniconfig: brain-dead simple config-ini parsing"
378 | category = "dev"
379 | optional = false
380 | python-versions = "*"
381 |
382 | [package.source]
383 | type = "legacy"
384 | url = "https://pypi.doubanio.com/simple"
385 | reference = "douban"
386 |
387 | [[package]]
388 | name = "itsdangerous"
389 | version = "2.0.1"
390 | description = "Safely pass data to untrusted environments and back."
391 | category = "main"
392 | optional = false
393 | python-versions = ">=3.6"
394 |
395 | [package.source]
396 | type = "legacy"
397 | url = "https://pypi.doubanio.com/simple"
398 | reference = "douban"
399 |
400 | [[package]]
401 | name = "jinja2"
402 | version = "3.0.1"
403 | description = "A very fast and expressive template engine."
404 | category = "main"
405 | optional = false
406 | python-versions = ">=3.6"
407 |
408 | [package.dependencies]
409 | MarkupSafe = ">=2.0"
410 |
411 | [package.extras]
412 | i18n = ["Babel (>=2.7)"]
413 |
414 | [package.source]
415 | type = "legacy"
416 | url = "https://pypi.doubanio.com/simple"
417 | reference = "douban"
418 |
419 | [[package]]
420 | name = "lxml"
421 | version = "4.6.3"
422 | description = "Powerful and Pythonic XML processing library combining libxml2/libxslt with the ElementTree API."
423 | category = "main"
424 | optional = false
425 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, != 3.4.*"
426 |
427 | [package.extras]
428 | cssselect = ["cssselect (>=0.7)"]
429 | html5 = ["html5lib"]
430 | htmlsoup = ["beautifulsoup4"]
431 | source = ["Cython (>=0.29.7)"]
432 |
433 | [package.source]
434 | type = "legacy"
435 | url = "https://pypi.doubanio.com/simple"
436 | reference = "douban"
437 |
438 | [[package]]
439 | name = "markdown"
440 | version = "3.3.4"
441 | description = "Python implementation of Markdown."
442 | category = "dev"
443 | optional = false
444 | python-versions = ">=3.6"
445 |
446 | [package.extras]
447 | testing = ["coverage", "pyyaml"]
448 |
449 | [package.source]
450 | type = "legacy"
451 | url = "https://pypi.doubanio.com/simple"
452 | reference = "douban"
453 |
454 | [[package]]
455 | name = "markupsafe"
456 | version = "2.0.1"
457 | description = "Safely add untrusted strings to HTML/XML markup."
458 | category = "main"
459 | optional = false
460 | python-versions = ">=3.6"
461 |
462 | [package.source]
463 | type = "legacy"
464 | url = "https://pypi.doubanio.com/simple"
465 | reference = "douban"
466 |
467 | [[package]]
468 | name = "mergedeep"
469 | version = "1.3.4"
470 | description = "A deep merge function for 🐍."
471 | category = "dev"
472 | optional = false
473 | python-versions = ">=3.6"
474 |
475 | [package.source]
476 | type = "legacy"
477 | url = "https://pypi.doubanio.com/simple"
478 | reference = "douban"
479 |
480 | [[package]]
481 | name = "mkdocs"
482 | version = "1.2.1"
483 | description = "Project documentation with Markdown."
484 | category = "dev"
485 | optional = false
486 | python-versions = ">=3.6"
487 |
488 | [package.dependencies]
489 | click = ">=3.3"
490 | ghp-import = ">=1.0"
491 | importlib-metadata = ">=3.10"
492 | Jinja2 = ">=2.10.1"
493 | Markdown = ">=3.2.1"
494 | mergedeep = ">=1.3.4"
495 | packaging = ">=20.5"
496 | PyYAML = ">=3.10"
497 | pyyaml-env-tag = ">=0.1"
498 | watchdog = ">=2.0"
499 |
500 | [package.extras]
501 | i18n = ["babel (>=2.9.0)"]
502 |
503 | [package.source]
504 | type = "legacy"
505 | url = "https://pypi.doubanio.com/simple"
506 | reference = "douban"
507 |
508 | [[package]]
509 | name = "mkdocs-material"
510 | version = "7.1.9"
511 | description = "A Material Design theme for MkDocs"
512 | category = "dev"
513 | optional = false
514 | python-versions = "*"
515 |
516 | [package.dependencies]
517 | markdown = ">=3.2"
518 | mkdocs = ">=1.1"
519 | mkdocs-material-extensions = ">=1.0"
520 | Pygments = ">=2.4"
521 | pymdown-extensions = ">=7.0"
522 |
523 | [package.source]
524 | type = "legacy"
525 | url = "https://pypi.doubanio.com/simple"
526 | reference = "douban"
527 |
528 | [[package]]
529 | name = "mkdocs-material-extensions"
530 | version = "1.0.1"
531 | description = "Extension pack for Python Markdown."
532 | category = "dev"
533 | optional = false
534 | python-versions = ">=3.5"
535 |
536 | [package.dependencies]
537 | mkdocs-material = ">=5.0.0"
538 |
539 | [package.source]
540 | type = "legacy"
541 | url = "https://pypi.doubanio.com/simple"
542 | reference = "douban"
543 |
544 | [[package]]
545 | name = "nodeenv"
546 | version = "1.6.0"
547 | description = "Node.js virtual environment builder"
548 | category = "dev"
549 | optional = false
550 | python-versions = "*"
551 |
552 | [package.source]
553 | type = "legacy"
554 | url = "https://pypi.doubanio.com/simple"
555 | reference = "douban"
556 |
557 | [[package]]
558 | name = "packaging"
559 | version = "20.9"
560 | description = "Core utilities for Python packages"
561 | category = "dev"
562 | optional = false
563 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
564 |
565 | [package.dependencies]
566 | pyparsing = ">=2.0.2"
567 |
568 | [package.source]
569 | type = "legacy"
570 | url = "https://pypi.doubanio.com/simple"
571 | reference = "douban"
572 |
573 | [[package]]
574 | name = "parse"
575 | version = "1.19.0"
576 | description = "parse() is the opposite of format()"
577 | category = "main"
578 | optional = false
579 | python-versions = "*"
580 |
581 | [package.source]
582 | type = "legacy"
583 | url = "https://pypi.doubanio.com/simple"
584 | reference = "douban"
585 |
586 | [[package]]
587 | name = "pep8"
588 | version = "1.7.1"
589 | description = "Python style guide checker"
590 | category = "dev"
591 | optional = false
592 | python-versions = "*"
593 |
594 | [package.source]
595 | type = "legacy"
596 | url = "https://pypi.doubanio.com/simple"
597 | reference = "douban"
598 |
599 | [[package]]
600 | name = "pluggy"
601 | version = "0.13.1"
602 | description = "plugin and hook calling mechanisms for python"
603 | category = "dev"
604 | optional = false
605 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
606 |
607 | [package.extras]
608 | dev = ["pre-commit", "tox"]
609 |
610 | [package.source]
611 | type = "legacy"
612 | url = "https://pypi.doubanio.com/simple"
613 | reference = "douban"
614 |
615 | [[package]]
616 | name = "pre-commit"
617 | version = "2.13.0"
618 | description = "A framework for managing and maintaining multi-language pre-commit hooks."
619 | category = "dev"
620 | optional = false
621 | python-versions = ">=3.6.1"
622 |
623 | [package.dependencies]
624 | cfgv = ">=2.0.0"
625 | identify = ">=1.0.0"
626 | nodeenv = ">=0.11.1"
627 | pyyaml = ">=5.1"
628 | toml = "*"
629 | virtualenv = ">=20.0.8"
630 |
631 | [package.source]
632 | type = "legacy"
633 | url = "https://pypi.doubanio.com/simple"
634 | reference = "douban"
635 |
636 | [[package]]
637 | name = "py"
638 | version = "1.10.0"
639 | description = "library with cross-python path, ini-parsing, io, code, log facilities"
640 | category = "dev"
641 | optional = false
642 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
643 |
644 | [package.source]
645 | type = "legacy"
646 | url = "https://pypi.doubanio.com/simple"
647 | reference = "douban"
648 |
649 | [[package]]
650 | name = "pygments"
651 | version = "2.9.0"
652 | description = "Pygments is a syntax highlighting package written in Python."
653 | category = "dev"
654 | optional = false
655 | python-versions = ">=3.5"
656 |
657 | [package.source]
658 | type = "legacy"
659 | url = "https://pypi.doubanio.com/simple"
660 | reference = "douban"
661 |
662 | [[package]]
663 | name = "pymdown-extensions"
664 | version = "8.2"
665 | description = "Extension pack for Python Markdown."
666 | category = "dev"
667 | optional = false
668 | python-versions = ">=3.6"
669 |
670 | [package.dependencies]
671 | Markdown = ">=3.2"
672 |
673 | [package.source]
674 | type = "legacy"
675 | url = "https://pypi.doubanio.com/simple"
676 | reference = "douban"
677 |
678 | [[package]]
679 | name = "pyparsing"
680 | version = "2.4.7"
681 | description = "Python parsing module"
682 | category = "dev"
683 | optional = false
684 | python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*"
685 |
686 | [package.source]
687 | type = "legacy"
688 | url = "https://pypi.doubanio.com/simple"
689 | reference = "douban"
690 |
691 | [[package]]
692 | name = "pytest"
693 | version = "6.2.4"
694 | description = "pytest: simple powerful testing with Python"
695 | category = "dev"
696 | optional = false
697 | python-versions = ">=3.6"
698 |
699 | [package.dependencies]
700 | atomicwrites = {version = ">=1.0", markers = "sys_platform == \"win32\""}
701 | attrs = ">=19.2.0"
702 | colorama = {version = "*", markers = "sys_platform == \"win32\""}
703 | iniconfig = "*"
704 | packaging = "*"
705 | pluggy = ">=0.12,<1.0.0a1"
706 | py = ">=1.8.2"
707 | toml = "*"
708 |
709 | [package.extras]
710 | testing = ["argcomplete", "hypothesis (>=3.56)", "mock", "nose", "requests", "xmlschema"]
711 |
712 | [package.source]
713 | type = "legacy"
714 | url = "https://pypi.doubanio.com/simple"
715 | reference = "douban"
716 |
717 | [[package]]
718 | name = "pytest-cache"
719 | version = "1.0"
720 | description = "pytest plugin with mechanisms for caching across test runs"
721 | category = "dev"
722 | optional = false
723 | python-versions = "*"
724 |
725 | [package.dependencies]
726 | execnet = ">=1.1.dev1"
727 | pytest = ">=2.2"
728 |
729 | [package.source]
730 | type = "legacy"
731 | url = "https://pypi.doubanio.com/simple"
732 | reference = "douban"
733 |
734 | [[package]]
735 | name = "pytest-cov"
736 | version = "2.12.1"
737 | description = "Pytest plugin for measuring coverage."
738 | category = "dev"
739 | optional = false
740 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
741 |
742 | [package.dependencies]
743 | coverage = ">=5.2.1"
744 | pytest = ">=4.6"
745 | toml = "*"
746 |
747 | [package.extras]
748 | testing = ["fields", "hunter", "process-tests", "six", "pytest-xdist", "virtualenv"]
749 |
750 | [package.source]
751 | type = "legacy"
752 | url = "https://pypi.doubanio.com/simple"
753 | reference = "douban"
754 |
755 | [[package]]
756 | name = "pytest-pep8"
757 | version = "1.0.6"
758 | description = "pytest plugin to check PEP8 requirements"
759 | category = "dev"
760 | optional = false
761 | python-versions = "*"
762 |
763 | [package.dependencies]
764 | pep8 = ">=1.3"
765 | pytest = ">=2.4.2"
766 | pytest-cache = "*"
767 |
768 | [package.source]
769 | type = "legacy"
770 | url = "https://pypi.doubanio.com/simple"
771 | reference = "douban"
772 |
773 | [[package]]
774 | name = "python-dateutil"
775 | version = "2.8.1"
776 | description = "Extensions to the standard Python datetime module"
777 | category = "dev"
778 | optional = false
779 | python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7"
780 |
781 | [package.dependencies]
782 | six = ">=1.5"
783 |
784 | [package.source]
785 | type = "legacy"
786 | url = "https://pypi.doubanio.com/simple"
787 | reference = "douban"
788 |
789 | [[package]]
790 | name = "pyyaml"
791 | version = "5.4.1"
792 | description = "YAML parser and emitter for Python"
793 | category = "dev"
794 | optional = false
795 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*"
796 |
797 | [package.source]
798 | type = "legacy"
799 | url = "https://pypi.doubanio.com/simple"
800 | reference = "douban"
801 |
802 | [[package]]
803 | name = "pyyaml-env-tag"
804 | version = "0.1"
805 | description = "A custom YAML tag for referencing environment variables in YAML files."
806 | category = "dev"
807 | optional = false
808 | python-versions = ">=3.6"
809 |
810 | [package.dependencies]
811 | pyyaml = "*"
812 |
813 | [package.source]
814 | type = "legacy"
815 | url = "https://pypi.doubanio.com/simple"
816 | reference = "douban"
817 |
818 | [[package]]
819 | name = "requests"
820 | version = "2.25.1"
821 | description = "Python HTTP for Humans."
822 | category = "main"
823 | optional = false
824 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
825 |
826 | [package.dependencies]
827 | certifi = ">=2017.4.17"
828 | chardet = ">=3.0.2,<5"
829 | idna = ">=2.5,<3"
830 | urllib3 = ">=1.21.1,<1.27"
831 |
832 | [package.extras]
833 | security = ["pyOpenSSL (>=0.14)", "cryptography (>=1.3.4)"]
834 | socks = ["PySocks (>=1.5.6,!=1.5.7)", "win-inet-pton"]
835 |
836 | [package.source]
837 | type = "legacy"
838 | url = "https://pypi.doubanio.com/simple"
839 | reference = "douban"
840 |
841 | [[package]]
842 | name = "selenium"
843 | version = "3.141.0"
844 | description = "Python bindings for Selenium"
845 | category = "main"
846 | optional = false
847 | python-versions = "*"
848 |
849 | [package.dependencies]
850 | urllib3 = "*"
851 |
852 | [package.source]
853 | type = "legacy"
854 | url = "https://pypi.doubanio.com/simple"
855 | reference = "douban"
856 |
857 | [[package]]
858 | name = "six"
859 | version = "1.16.0"
860 | description = "Python 2 and 3 compatibility utilities"
861 | category = "dev"
862 | optional = false
863 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*"
864 |
865 | [package.source]
866 | type = "legacy"
867 | url = "https://pypi.doubanio.com/simple"
868 | reference = "douban"
869 |
870 | [[package]]
871 | name = "soupsieve"
872 | version = "2.2.1"
873 | description = "A modern CSS selector implementation for Beautiful Soup."
874 | category = "dev"
875 | optional = false
876 | python-versions = ">=3.6"
877 |
878 | [package.source]
879 | type = "legacy"
880 | url = "https://pypi.doubanio.com/simple"
881 | reference = "douban"
882 |
883 | [[package]]
884 | name = "toml"
885 | version = "0.10.2"
886 | description = "Python Library for Tom's Obvious, Minimal Language"
887 | category = "dev"
888 | optional = false
889 | python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*"
890 |
891 | [package.source]
892 | type = "legacy"
893 | url = "https://pypi.doubanio.com/simple"
894 | reference = "douban"
895 |
896 | [[package]]
897 | name = "ujson"
898 | version = "4.0.2"
899 | description = "Ultra fast JSON encoder and decoder for Python"
900 | category = "dev"
901 | optional = false
902 | python-versions = ">=3.6"
903 |
904 | [package.source]
905 | type = "legacy"
906 | url = "https://pypi.doubanio.com/simple"
907 | reference = "douban"
908 |
909 | [[package]]
910 | name = "urllib3"
911 | version = "1.26.6"
912 | description = "HTTP library with thread-safe connection pooling, file post, and more."
913 | category = "main"
914 | optional = false
915 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, <4"
916 |
917 | [package.extras]
918 | brotli = ["brotlipy (>=0.6.0)"]
919 | secure = ["pyOpenSSL (>=0.14)", "cryptography (>=1.3.4)", "idna (>=2.0.0)", "certifi", "ipaddress"]
920 | socks = ["PySocks (>=1.5.6,!=1.5.7,<2.0)"]
921 |
922 | [package.source]
923 | type = "legacy"
924 | url = "https://pypi.doubanio.com/simple"
925 | reference = "douban"
926 |
927 | [[package]]
928 | name = "virtualenv"
929 | version = "20.4.7"
930 | description = "Virtual Python Environment builder"
931 | category = "dev"
932 | optional = false
933 | python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,>=2.7"
934 |
935 | [package.dependencies]
936 | appdirs = ">=1.4.3,<2"
937 | distlib = ">=0.3.1,<1"
938 | filelock = ">=3.0.0,<4"
939 | six = ">=1.9.0,<2"
940 |
941 | [package.extras]
942 | docs = ["proselint (>=0.10.2)", "sphinx (>=3)", "sphinx-argparse (>=0.2.5)", "sphinx-rtd-theme (>=0.4.3)", "towncrier (>=19.9.0rc1)"]
943 | testing = ["coverage (>=4)", "coverage-enable-subprocess (>=1)", "flaky (>=3)", "pytest (>=4)", "pytest-env (>=0.6.2)", "pytest-freezegun (>=0.4.1)", "pytest-mock (>=2)", "pytest-randomly (>=1)", "pytest-timeout (>=1)", "packaging (>=20.0)", "xonsh (>=0.9.16)"]
944 |
945 | [package.source]
946 | type = "legacy"
947 | url = "https://pypi.doubanio.com/simple"
948 | reference = "douban"
949 |
950 | [[package]]
951 | name = "waitress"
952 | version = "2.0.0"
953 | description = "Waitress WSGI server"
954 | category = "dev"
955 | optional = false
956 | python-versions = ">=3.6.0"
957 |
958 | [package.extras]
959 | docs = ["Sphinx (>=1.8.1)", "docutils", "pylons-sphinx-themes (>=1.0.9)"]
960 | testing = ["pytest", "pytest-cover", "coverage (>=5.0)"]
961 |
962 | [package.source]
963 | type = "legacy"
964 | url = "https://pypi.doubanio.com/simple"
965 | reference = "douban"
966 |
967 | [[package]]
968 | name = "watchdog"
969 | version = "2.1.3"
970 | description = "Filesystem events monitoring"
971 | category = "dev"
972 | optional = false
973 | python-versions = ">=3.6"
974 |
975 | [package.extras]
976 | watchmedo = ["PyYAML (>=3.10)", "argh (>=0.24.1)"]
977 |
978 | [package.source]
979 | type = "legacy"
980 | url = "https://pypi.doubanio.com/simple"
981 | reference = "douban"
982 |
983 | [[package]]
984 | name = "webob"
985 | version = "1.8.7"
986 | description = "WSGI request and response object"
987 | category = "dev"
988 | optional = false
989 | python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*"
990 |
991 | [package.extras]
992 | docs = ["Sphinx (>=1.7.5)", "pylons-sphinx-themes"]
993 | testing = ["pytest (>=3.1.0)", "coverage", "pytest-cov", "pytest-xdist"]
994 |
995 | [package.source]
996 | type = "legacy"
997 | url = "https://pypi.doubanio.com/simple"
998 | reference = "douban"
999 |
1000 | [[package]]
1001 | name = "webtest"
1002 | version = "2.0.35"
1003 | description = "Helper to test WSGI applications"
1004 | category = "dev"
1005 | optional = false
1006 | python-versions = "*"
1007 |
1008 | [package.dependencies]
1009 | beautifulsoup4 = "*"
1010 | six = "*"
1011 | waitress = ">=0.8.5"
1012 | WebOb = ">=1.2"
1013 |
1014 | [package.extras]
1015 | docs = ["Sphinx (>=1.8.1)", "docutils", "pylons-sphinx-themes (>=1.0.8)"]
1016 | tests = ["nose (<1.3.0)", "coverage", "mock", "pastedeploy", "wsgiproxy2", "pyquery"]
1017 |
1018 | [package.source]
1019 | type = "legacy"
1020 | url = "https://pypi.doubanio.com/simple"
1021 | reference = "douban"
1022 |
1023 | [[package]]
1024 | name = "werkzeug"
1025 | version = "2.0.1"
1026 | description = "The comprehensive WSGI web application library."
1027 | category = "main"
1028 | optional = false
1029 | python-versions = ">=3.6"
1030 |
1031 | [package.extras]
1032 | watchdog = ["watchdog"]
1033 |
1034 | [package.source]
1035 | type = "legacy"
1036 | url = "https://pypi.doubanio.com/simple"
1037 | reference = "douban"
1038 |
1039 | [[package]]
1040 | name = "zipp"
1041 | version = "3.4.1"
1042 | description = "Backport of pathlib-compatible object wrapper for zip files"
1043 | category = "dev"
1044 | optional = false
1045 | python-versions = ">=3.6"
1046 |
1047 | [package.extras]
1048 | docs = ["sphinx", "jaraco.packaging (>=8.2)", "rst.linker (>=1.9)"]
1049 | testing = ["pytest (>=4.6)", "pytest-checkdocs (>=1.2.3)", "pytest-flake8", "pytest-cov", "pytest-enabler", "jaraco.itertools", "func-timeout", "pytest-black (>=0.3.7)", "pytest-mypy"]
1050 |
1051 | [package.source]
1052 | type = "legacy"
1053 | url = "https://pypi.doubanio.com/simple"
1054 | reference = "douban"
1055 |
1056 | [metadata]
1057 | lock-version = "1.1"
1058 | python-versions = "^3.8"
1059 | content-hash = "89f8d652f628c35ee821d1241dc91923b928719d3d3bd38d61caa9f476016c4b"
1060 |
1061 | [metadata.files]
1062 | appdirs = [
1063 | {file = "appdirs-1.4.4-py2.py3-none-any.whl", hash = "sha256:a841dacd6b99318a741b166adb07e19ee71a274450e68237b4650ca1055ab128"},
1064 | {file = "appdirs-1.4.4.tar.gz", hash = "sha256:7d5d0167b2b1ba821647616af46a749d1c653740dd0d2415100fe26e27afdf41"},
1065 | ]
1066 | atomicwrites = [
1067 | {file = "atomicwrites-1.4.0-py2.py3-none-any.whl", hash = "sha256:6d1784dea7c0c8d4a5172b6c620f40b6e4cbfdf96d783691f2e1302a7b88e197"},
1068 | {file = "atomicwrites-1.4.0.tar.gz", hash = "sha256:ae70396ad1a434f9c7046fd2dd196fc04b12f9e91ffb859164193be8b6168a7a"},
1069 | ]
1070 | attrs = [
1071 | {file = "attrs-21.2.0-py2.py3-none-any.whl", hash = "sha256:149e90d6d8ac20db7a955ad60cf0e6881a3f20d37096140088356da6c716b0b1"},
1072 | {file = "attrs-21.2.0.tar.gz", hash = "sha256:ef6aaac3ca6cd92904cdd0d83f629a15f18053ec84e6432106f7a4d04ae4f5fb"},
1073 | ]
1074 | beautifulsoup4 = [
1075 | {file = "beautifulsoup4-4.9.3-py2-none-any.whl", hash = "sha256:4c98143716ef1cb40bf7f39a8e3eec8f8b009509e74904ba3a7b315431577e35"},
1076 | {file = "beautifulsoup4-4.9.3-py3-none-any.whl", hash = "sha256:fff47e031e34ec82bf17e00da8f592fe7de69aeea38be00523c04623c04fb666"},
1077 | {file = "beautifulsoup4-4.9.3.tar.gz", hash = "sha256:84729e322ad1d5b4d25f805bfa05b902dd96450f43842c4e99067d5e1369eb25"},
1078 | ]
1079 | cchardet = [
1080 | {file = "cchardet-2.1.7-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:c6f70139aaf47ffb94d89db603af849b82efdf756f187cdd3e566e30976c519f"},
1081 | {file = "cchardet-2.1.7-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:5a25f9577e9bebe1a085eec2d6fdd72b7a9dd680811bba652ea6090fb2ff472f"},
1082 | {file = "cchardet-2.1.7-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:6b6397d8a32b976a333bdae060febd39ad5479817fabf489e5596a588ad05133"},
1083 | {file = "cchardet-2.1.7-cp36-cp36m-manylinux2010_i686.whl", hash = "sha256:228d2533987c450f39acf7548f474dd6814c446e9d6bd228e8f1d9a2d210f10b"},
1084 | {file = "cchardet-2.1.7-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:54341e7e1ba9dc0add4c9d23b48d3a94e2733065c13920e85895f944596f6150"},
1085 | {file = "cchardet-2.1.7-cp36-cp36m-win32.whl", hash = "sha256:eee4f5403dc3a37a1ca9ab87db32b48dc7e190ef84601068f45397144427cc5e"},
1086 | {file = "cchardet-2.1.7-cp36-cp36m-win_amd64.whl", hash = "sha256:f86e0566cb61dc4397297696a4a1b30f6391b50bc52b4f073507a48466b6255a"},
1087 | {file = "cchardet-2.1.7-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:302aa443ae2526755d412c9631136bdcd1374acd08e34f527447f06f3c2ddb98"},
1088 | {file = "cchardet-2.1.7-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:70eeae8aaf61192e9b247cf28969faef00578becd2602526ecd8ae7600d25e0e"},
1089 | {file = "cchardet-2.1.7-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:a39526c1c526843965cec589a6f6b7c2ab07e3e56dc09a7f77a2be6a6afa4636"},
1090 | {file = "cchardet-2.1.7-cp37-cp37m-manylinux2010_i686.whl", hash = "sha256:b154effa12886e9c18555dfc41a110f601f08d69a71809c8d908be4b1ab7314f"},
1091 | {file = "cchardet-2.1.7-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:ec3eb5a9c475208cf52423524dcaf713c394393e18902e861f983c38eeb77f18"},
1092 | {file = "cchardet-2.1.7-cp37-cp37m-win32.whl", hash = "sha256:50ad671e8d6c886496db62c3bd68b8d55060688c655873aa4ce25ca6105409a1"},
1093 | {file = "cchardet-2.1.7-cp37-cp37m-win_amd64.whl", hash = "sha256:54d0b26fd0cd4099f08fb9c167600f3e83619abefeaa68ad823cc8ac1f7bcc0c"},
1094 | {file = "cchardet-2.1.7-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:b59ddc615883835e03c26f81d5fc3671fab2d32035c87f50862de0da7d7db535"},
1095 | {file = "cchardet-2.1.7-cp38-cp38-manylinux1_i686.whl", hash = "sha256:27a9ba87c9f99e0618e1d3081189b1217a7d110e5c5597b0b7b7c3fedd1c340a"},
1096 | {file = "cchardet-2.1.7-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:90086e5645f8a1801350f4cc6cb5d5bf12d3fa943811bb08667744ec1ecc9ccd"},
1097 | {file = "cchardet-2.1.7-cp38-cp38-manylinux2010_i686.whl", hash = "sha256:45456c59ec349b29628a3c6bfb86d818ec3a6fbb7eb72de4ff3bd4713681c0e3"},
1098 | {file = "cchardet-2.1.7-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:f16517f3697569822c6d09671217fdeab61dfebc7acb5068634d6b0728b86c0b"},
1099 | {file = "cchardet-2.1.7-cp38-cp38-win32.whl", hash = "sha256:0b859069bbb9d27c78a2c9eb997e6f4b738db2d7039a03f8792b4058d61d1109"},
1100 | {file = "cchardet-2.1.7-cp38-cp38-win_amd64.whl", hash = "sha256:273699c4e5cd75377776501b72a7b291a988c6eec259c29505094553ee505597"},
1101 | {file = "cchardet-2.1.7-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:48ba829badef61441e08805cfa474ccd2774be2ff44b34898f5854168c596d4d"},
1102 | {file = "cchardet-2.1.7-cp39-cp39-manylinux1_i686.whl", hash = "sha256:bd7f262f41fd9caf5a5f09207a55861a67af6ad5c66612043ed0f81c58cdf376"},
1103 | {file = "cchardet-2.1.7-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:fdac1e4366d0579fff056d1280b8dc6348be964fda8ebb627c0269e097ab37fa"},
1104 | {file = "cchardet-2.1.7-cp39-cp39-manylinux2010_i686.whl", hash = "sha256:80e6faae75ecb9be04a7b258dc4750d459529debb6b8dee024745b7b5a949a34"},
1105 | {file = "cchardet-2.1.7-cp39-cp39-manylinux2010_x86_64.whl", hash = "sha256:c96aee9ebd1147400e608a3eff97c44f49811f8904e5a43069d55603ac4d8c97"},
1106 | {file = "cchardet-2.1.7-cp39-cp39-win32.whl", hash = "sha256:2309ff8fc652b0fc3c0cff5dbb172530c7abb92fe9ba2417c9c0bcf688463c1c"},
1107 | {file = "cchardet-2.1.7-cp39-cp39-win_amd64.whl", hash = "sha256:24974b3e40fee9e7557bb352be625c39ec6f50bc2053f44a3d1191db70b51675"},
1108 | {file = "cchardet-2.1.7.tar.gz", hash = "sha256:c428b6336545053c2589f6caf24ea32276c6664cb86db817e03a94c60afa0eaf"},
1109 | ]
1110 | certifi = [
1111 | {file = "certifi-2021.5.30-py2.py3-none-any.whl", hash = "sha256:50b1e4f8446b06f41be7dd6338db18e0990601dce795c2b1686458aa7e8fa7d8"},
1112 | {file = "certifi-2021.5.30.tar.gz", hash = "sha256:2bbf76fd432960138b3ef6dda3dde0544f27cbf8546c458e60baf371917ba9ee"},
1113 | ]
1114 | cfgv = [
1115 | {file = "cfgv-3.3.0-py2.py3-none-any.whl", hash = "sha256:b449c9c6118fe8cca7fa5e00b9ec60ba08145d281d52164230a69211c5d597a1"},
1116 | {file = "cfgv-3.3.0.tar.gz", hash = "sha256:9e600479b3b99e8af981ecdfc80a0296104ee610cab48a5ae4ffd0b668650eb1"},
1117 | ]
1118 | chardet = [
1119 | {file = "chardet-4.0.0-py2.py3-none-any.whl", hash = "sha256:f864054d66fd9118f2e67044ac8981a54775ec5b67aed0441892edb553d21da5"},
1120 | {file = "chardet-4.0.0.tar.gz", hash = "sha256:0d6f53a15db4120f2b08c94f11e7d93d2c911ee118b6b30a04ec3ee8310179fa"},
1121 | ]
1122 | click = [
1123 | {file = "click-8.0.1-py3-none-any.whl", hash = "sha256:fba402a4a47334742d782209a7c79bc448911afe1149d07bdabdf480b3e2f4b6"},
1124 | {file = "click-8.0.1.tar.gz", hash = "sha256:8c04c11192119b1ef78ea049e0a6f0463e4c48ef00a30160c704337586f3ad7a"},
1125 | ]
1126 | codecov = [
1127 | {file = "codecov-2.1.11-py2.py3-none-any.whl", hash = "sha256:ba8553a82942ce37d4da92b70ffd6d54cf635fc1793ab0a7dc3fecd6ebfb3df8"},
1128 | {file = "codecov-2.1.11.tar.gz", hash = "sha256:6cde272454009d27355f9434f4e49f238c0273b216beda8472a65dc4957f473b"},
1129 | ]
1130 | colorama = [
1131 | {file = "colorama-0.4.4-py2.py3-none-any.whl", hash = "sha256:9f47eda37229f68eee03b24b9748937c7dc3868f906e8ba69fbcbdd3bc5dc3e2"},
1132 | {file = "colorama-0.4.4.tar.gz", hash = "sha256:5941b2b48a20143d2267e95b1c2a7603ce057ee39fd88e7329b0c292aa16869b"},
1133 | ]
1134 | coverage = [
1135 | {file = "coverage-5.5-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:b6d534e4b2ab35c9f93f46229363e17f63c53ad01330df9f2d6bd1187e5eaacf"},
1136 | {file = "coverage-5.5-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:b7895207b4c843c76a25ab8c1e866261bcfe27bfaa20c192de5190121770672b"},
1137 | {file = "coverage-5.5-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:c2723d347ab06e7ddad1a58b2a821218239249a9e4365eaff6649d31180c1669"},
1138 | {file = "coverage-5.5-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:900fbf7759501bc7807fd6638c947d7a831fc9fdf742dc10f02956ff7220fa90"},
1139 | {file = "coverage-5.5-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:004d1880bed2d97151facef49f08e255a20ceb6f9432df75f4eef018fdd5a78c"},
1140 | {file = "coverage-5.5-cp27-cp27m-win32.whl", hash = "sha256:06191eb60f8d8a5bc046f3799f8a07a2d7aefb9504b0209aff0b47298333302a"},
1141 | {file = "coverage-5.5-cp27-cp27m-win_amd64.whl", hash = "sha256:7501140f755b725495941b43347ba8a2777407fc7f250d4f5a7d2a1050ba8e82"},
1142 | {file = "coverage-5.5-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:372da284cfd642d8e08ef606917846fa2ee350f64994bebfbd3afb0040436905"},
1143 | {file = "coverage-5.5-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:8963a499849a1fc54b35b1c9f162f4108017b2e6db2c46c1bed93a72262ed083"},
1144 | {file = "coverage-5.5-cp27-cp27mu-manylinux2010_i686.whl", hash = "sha256:869a64f53488f40fa5b5b9dcb9e9b2962a66a87dab37790f3fcfb5144b996ef5"},
1145 | {file = "coverage-5.5-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:4a7697d8cb0f27399b0e393c0b90f0f1e40c82023ea4d45d22bce7032a5d7b81"},
1146 | {file = "coverage-5.5-cp310-cp310-macosx_10_14_x86_64.whl", hash = "sha256:8d0a0725ad7c1a0bcd8d1b437e191107d457e2ec1084b9f190630a4fb1af78e6"},
1147 | {file = "coverage-5.5-cp310-cp310-manylinux1_x86_64.whl", hash = "sha256:51cb9476a3987c8967ebab3f0fe144819781fca264f57f89760037a2ea191cb0"},
1148 | {file = "coverage-5.5-cp310-cp310-win_amd64.whl", hash = "sha256:c0891a6a97b09c1f3e073a890514d5012eb256845c451bd48f7968ef939bf4ae"},
1149 | {file = "coverage-5.5-cp35-cp35m-macosx_10_9_x86_64.whl", hash = "sha256:3487286bc29a5aa4b93a072e9592f22254291ce96a9fbc5251f566b6b7343cdb"},
1150 | {file = "coverage-5.5-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:deee1077aae10d8fa88cb02c845cfba9b62c55e1183f52f6ae6a2df6a2187160"},
1151 | {file = "coverage-5.5-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:f11642dddbb0253cc8853254301b51390ba0081750a8ac03f20ea8103f0c56b6"},
1152 | {file = "coverage-5.5-cp35-cp35m-manylinux2010_i686.whl", hash = "sha256:6c90e11318f0d3c436a42409f2749ee1a115cd8b067d7f14c148f1ce5574d701"},
1153 | {file = "coverage-5.5-cp35-cp35m-manylinux2010_x86_64.whl", hash = "sha256:30c77c1dc9f253283e34c27935fded5015f7d1abe83bc7821680ac444eaf7793"},
1154 | {file = "coverage-5.5-cp35-cp35m-win32.whl", hash = "sha256:9a1ef3b66e38ef8618ce5fdc7bea3d9f45f3624e2a66295eea5e57966c85909e"},
1155 | {file = "coverage-5.5-cp35-cp35m-win_amd64.whl", hash = "sha256:972c85d205b51e30e59525694670de6a8a89691186012535f9d7dbaa230e42c3"},
1156 | {file = "coverage-5.5-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:af0e781009aaf59e25c5a678122391cb0f345ac0ec272c7961dc5455e1c40066"},
1157 | {file = "coverage-5.5-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:74d881fc777ebb11c63736622b60cb9e4aee5cace591ce274fb69e582a12a61a"},
1158 | {file = "coverage-5.5-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:92b017ce34b68a7d67bd6d117e6d443a9bf63a2ecf8567bb3d8c6c7bc5014465"},
1159 | {file = "coverage-5.5-cp36-cp36m-manylinux2010_i686.whl", hash = "sha256:d636598c8305e1f90b439dbf4f66437de4a5e3c31fdf47ad29542478c8508bbb"},
1160 | {file = "coverage-5.5-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:41179b8a845742d1eb60449bdb2992196e211341818565abded11cfa90efb821"},
1161 | {file = "coverage-5.5-cp36-cp36m-win32.whl", hash = "sha256:040af6c32813fa3eae5305d53f18875bedd079960822ef8ec067a66dd8afcd45"},
1162 | {file = "coverage-5.5-cp36-cp36m-win_amd64.whl", hash = "sha256:5fec2d43a2cc6965edc0bb9e83e1e4b557f76f843a77a2496cbe719583ce8184"},
1163 | {file = "coverage-5.5-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:18ba8bbede96a2c3dde7b868de9dcbd55670690af0988713f0603f037848418a"},
1164 | {file = "coverage-5.5-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:2910f4d36a6a9b4214bb7038d537f015346f413a975d57ca6b43bf23d6563b53"},
1165 | {file = "coverage-5.5-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:f0b278ce10936db1a37e6954e15a3730bea96a0997c26d7fee88e6c396c2086d"},
1166 | {file = "coverage-5.5-cp37-cp37m-manylinux2010_i686.whl", hash = "sha256:796c9c3c79747146ebd278dbe1e5c5c05dd6b10cc3bcb8389dfdf844f3ead638"},
1167 | {file = "coverage-5.5-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:53194af30d5bad77fcba80e23a1441c71abfb3e01192034f8246e0d8f99528f3"},
1168 | {file = "coverage-5.5-cp37-cp37m-win32.whl", hash = "sha256:184a47bbe0aa6400ed2d41d8e9ed868b8205046518c52464fde713ea06e3a74a"},
1169 | {file = "coverage-5.5-cp37-cp37m-win_amd64.whl", hash = "sha256:2949cad1c5208b8298d5686d5a85b66aae46d73eec2c3e08c817dd3513e5848a"},
1170 | {file = "coverage-5.5-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:217658ec7187497e3f3ebd901afdca1af062b42cfe3e0dafea4cced3983739f6"},
1171 | {file = "coverage-5.5-cp38-cp38-manylinux1_i686.whl", hash = "sha256:1aa846f56c3d49205c952d8318e76ccc2ae23303351d9270ab220004c580cfe2"},
1172 | {file = "coverage-5.5-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:24d4a7de75446be83244eabbff746d66b9240ae020ced65d060815fac3423759"},
1173 | {file = "coverage-5.5-cp38-cp38-manylinux2010_i686.whl", hash = "sha256:d1f8bf7b90ba55699b3a5e44930e93ff0189aa27186e96071fac7dd0d06a1873"},
1174 | {file = "coverage-5.5-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:970284a88b99673ccb2e4e334cfb38a10aab7cd44f7457564d11898a74b62d0a"},
1175 | {file = "coverage-5.5-cp38-cp38-win32.whl", hash = "sha256:01d84219b5cdbfc8122223b39a954820929497a1cb1422824bb86b07b74594b6"},
1176 | {file = "coverage-5.5-cp38-cp38-win_amd64.whl", hash = "sha256:2e0d881ad471768bf6e6c2bf905d183543f10098e3b3640fc029509530091502"},
1177 | {file = "coverage-5.5-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:d1f9ce122f83b2305592c11d64f181b87153fc2c2bbd3bb4a3dde8303cfb1a6b"},
1178 | {file = "coverage-5.5-cp39-cp39-manylinux1_i686.whl", hash = "sha256:13c4ee887eca0f4c5a247b75398d4114c37882658300e153113dafb1d76de529"},
1179 | {file = "coverage-5.5-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:52596d3d0e8bdf3af43db3e9ba8dcdaac724ba7b5ca3f6358529d56f7a166f8b"},
1180 | {file = "coverage-5.5-cp39-cp39-manylinux2010_i686.whl", hash = "sha256:2cafbbb3af0733db200c9b5f798d18953b1a304d3f86a938367de1567f4b5bff"},
1181 | {file = "coverage-5.5-cp39-cp39-manylinux2010_x86_64.whl", hash = "sha256:44d654437b8ddd9eee7d1eaee28b7219bec228520ff809af170488fd2fed3e2b"},
1182 | {file = "coverage-5.5-cp39-cp39-win32.whl", hash = "sha256:d314ed732c25d29775e84a960c3c60808b682c08d86602ec2c3008e1202e3bb6"},
1183 | {file = "coverage-5.5-cp39-cp39-win_amd64.whl", hash = "sha256:13034c4409db851670bc9acd836243aeee299949bd5673e11844befcb0149f03"},
1184 | {file = "coverage-5.5-pp36-none-any.whl", hash = "sha256:f030f8873312a16414c0d8e1a1ddff2d3235655a2174e3648b4fa66b3f2f1079"},
1185 | {file = "coverage-5.5-pp37-none-any.whl", hash = "sha256:2a3859cb82dcbda1cfd3e6f71c27081d18aa251d20a17d87d26d4cd216fb0af4"},
1186 | {file = "coverage-5.5.tar.gz", hash = "sha256:ebe78fe9a0e874362175b02371bdfbee64d8edc42a044253ddf4ee7d3c15212c"},
1187 | ]
1188 | cssselect = [
1189 | {file = "cssselect-1.1.0-py2.py3-none-any.whl", hash = "sha256:f612ee47b749c877ebae5bb77035d8f4202c6ad0f0fc1271b3c18ad6c4468ecf"},
1190 | {file = "cssselect-1.1.0.tar.gz", hash = "sha256:f95f8dedd925fd8f54edb3d2dfb44c190d9d18512377d3c1e2388d16126879bc"},
1191 | ]
1192 | distlib = [
1193 | {file = "distlib-0.3.2-py2.py3-none-any.whl", hash = "sha256:23e223426b28491b1ced97dc3bbe183027419dfc7982b4fa2f05d5f3ff10711c"},
1194 | {file = "distlib-0.3.2.zip", hash = "sha256:106fef6dc37dd8c0e2c0a60d3fca3e77460a48907f335fa28420463a6f799736"},
1195 | ]
1196 | execnet = [
1197 | {file = "execnet-1.9.0-py2.py3-none-any.whl", hash = "sha256:a295f7cc774947aac58dde7fdc85f4aa00c42adf5d8f5468fc630c1acf30a142"},
1198 | {file = "execnet-1.9.0.tar.gz", hash = "sha256:8f694f3ba9cc92cab508b152dcfe322153975c29bda272e2fd7f3f00f36e47c5"},
1199 | ]
1200 | filelock = [
1201 | {file = "filelock-3.0.12-py3-none-any.whl", hash = "sha256:929b7d63ec5b7d6b71b0fa5ac14e030b3f70b75747cef1b10da9b879fef15836"},
1202 | {file = "filelock-3.0.12.tar.gz", hash = "sha256:18d82244ee114f543149c66a6e0c14e9c4f8a1044b5cdaadd0f82159d6a6ff59"},
1203 | ]
1204 | flask = [
1205 | {file = "Flask-2.0.1-py3-none-any.whl", hash = "sha256:a6209ca15eb63fc9385f38e452704113d679511d9574d09b2cf9183ae7d20dc9"},
1206 | {file = "Flask-2.0.1.tar.gz", hash = "sha256:1c4c257b1892aec1398784c63791cbaa43062f1f7aeb555c4da961b20ee68f55"},
1207 | ]
1208 | ghp-import = [
1209 | {file = "ghp-import-2.0.1.tar.gz", hash = "sha256:753de2eace6e0f7d4edfb3cce5e3c3b98cd52aadb80163303d1d036bda7b4483"},
1210 | ]
1211 | html2text = [
1212 | {file = "html2text-2020.1.16-py3-none-any.whl", hash = "sha256:c7c629882da0cf377d66f073329ccf34a12ed2adf0169b9285ae4e63ef54c82b"},
1213 | {file = "html2text-2020.1.16.tar.gz", hash = "sha256:e296318e16b059ddb97f7a8a1d6a5c1d7af4544049a01e261731d2d5cc277bbb"},
1214 | ]
1215 | htmlfetcher = [
1216 | {file = "htmlfetcher-0.0.6.tar.gz", hash = "sha256:b6233e0a262b019ca688661024cb175028fe7e8dfe6724a8ca9495c265535859"},
1217 | ]
1218 | htmlparsing = [
1219 | {file = "htmlparsing-0.1.5.tar.gz", hash = "sha256:226a22527f237830f8158cb72b37ace4cb0a29e072959e118500446382f27e94"},
1220 | ]
1221 | identify = [
1222 | {file = "identify-2.2.10-py2.py3-none-any.whl", hash = "sha256:18d0c531ee3dbc112fa6181f34faa179de3f57ea57ae2899754f16a7e0ff6421"},
1223 | {file = "identify-2.2.10.tar.gz", hash = "sha256:5b41f71471bc738e7b586308c3fca172f78940195cb3bf6734c1e66fdac49306"},
1224 | ]
1225 | idna = [
1226 | {file = "idna-2.10-py2.py3-none-any.whl", hash = "sha256:b97d804b1e9b523befed77c48dacec60e6dcb0b5391d57af6a65a312a90648c0"},
1227 | {file = "idna-2.10.tar.gz", hash = "sha256:b307872f855b18632ce0c21c5e45be78c0ea7ae4c15c828c20788b26921eb3f6"},
1228 | ]
1229 | importlib-metadata = [
1230 | {file = "importlib_metadata-4.6.0-py3-none-any.whl", hash = "sha256:c6513572926a96458f8c8f725bf0e00108fba0c9583ade9bd15b869c9d726e33"},
1231 | {file = "importlib_metadata-4.6.0.tar.gz", hash = "sha256:4a5611fea3768d3d967c447ab4e93f567d95db92225b43b7b238dbfb855d70bb"},
1232 | ]
1233 | iniconfig = [
1234 | {file = "iniconfig-1.1.1-py2.py3-none-any.whl", hash = "sha256:011e24c64b7f47f6ebd835bb12a743f2fbe9a26d4cecaa7f53bc4f35ee9da8b3"},
1235 | {file = "iniconfig-1.1.1.tar.gz", hash = "sha256:bc3af051d7d14b2ee5ef9969666def0cd1a000e121eaea580d4a313df4b37f32"},
1236 | ]
1237 | itsdangerous = [
1238 | {file = "itsdangerous-2.0.1-py3-none-any.whl", hash = "sha256:5174094b9637652bdb841a3029700391451bd092ba3db90600dea710ba28e97c"},
1239 | {file = "itsdangerous-2.0.1.tar.gz", hash = "sha256:9e724d68fc22902a1435351f84c3fb8623f303fffcc566a4cb952df8c572cff0"},
1240 | ]
1241 | jinja2 = [
1242 | {file = "Jinja2-3.0.1-py3-none-any.whl", hash = "sha256:1f06f2da51e7b56b8f238affdd6b4e2c61e39598a378cc49345bc1bd42a978a4"},
1243 | {file = "Jinja2-3.0.1.tar.gz", hash = "sha256:703f484b47a6af502e743c9122595cc812b0271f661722403114f71a79d0f5a4"},
1244 | ]
1245 | lxml = [
1246 | {file = "lxml-4.6.3-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:df7c53783a46febb0e70f6b05df2ba104610f2fb0d27023409734a3ecbb78fb2"},
1247 | {file = "lxml-4.6.3-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:1b7584d421d254ab86d4f0b13ec662a9014397678a7c4265a02a6d7c2b18a75f"},
1248 | {file = "lxml-4.6.3-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:079f3ae844f38982d156efce585bc540c16a926d4436712cf4baee0cce487a3d"},
1249 | {file = "lxml-4.6.3-cp27-cp27m-win32.whl", hash = "sha256:bc4313cbeb0e7a416a488d72f9680fffffc645f8a838bd2193809881c67dd106"},
1250 | {file = "lxml-4.6.3-cp27-cp27m-win_amd64.whl", hash = "sha256:8157dadbb09a34a6bd95a50690595e1fa0af1a99445e2744110e3dca7831c4ee"},
1251 | {file = "lxml-4.6.3-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:7728e05c35412ba36d3e9795ae8995e3c86958179c9770e65558ec3fdfd3724f"},
1252 | {file = "lxml-4.6.3-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:4bff24dfeea62f2e56f5bab929b4428ae6caba2d1eea0c2d6eb618e30a71e6d4"},
1253 | {file = "lxml-4.6.3-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:74f7d8d439b18fa4c385f3f5dfd11144bb87c1da034a466c5b5577d23a1d9b51"},
1254 | {file = "lxml-4.6.3-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:f90ba11136bfdd25cae3951af8da2e95121c9b9b93727b1b896e3fa105b2f586"},
1255 | {file = "lxml-4.6.3-cp35-cp35m-manylinux2010_i686.whl", hash = "sha256:4c61b3a0db43a1607d6264166b230438f85bfed02e8cff20c22e564d0faff354"},
1256 | {file = "lxml-4.6.3-cp35-cp35m-manylinux2014_x86_64.whl", hash = "sha256:5c8c163396cc0df3fd151b927e74f6e4acd67160d6c33304e805b84293351d16"},
1257 | {file = "lxml-4.6.3-cp35-cp35m-win32.whl", hash = "sha256:f2380a6376dfa090227b663f9678150ef27543483055cc327555fb592c5967e2"},
1258 | {file = "lxml-4.6.3-cp35-cp35m-win_amd64.whl", hash = "sha256:c4f05c5a7c49d2fb70223d0d5bcfbe474cf928310ac9fa6a7c6dddc831d0b1d4"},
1259 | {file = "lxml-4.6.3-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:d2e35d7bf1c1ac8c538f88d26b396e73dd81440d59c1ef8522e1ea77b345ede4"},
1260 | {file = "lxml-4.6.3-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:289e9ca1a9287f08daaf796d96e06cb2bc2958891d7911ac7cae1c5f9e1e0ee3"},
1261 | {file = "lxml-4.6.3-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:bccbfc27563652de7dc9bdc595cb25e90b59c5f8e23e806ed0fd623755b6565d"},
1262 | {file = "lxml-4.6.3-cp36-cp36m-manylinux2010_i686.whl", hash = "sha256:d916d31fd85b2f78c76400d625076d9124de3e4bda8b016d25a050cc7d603f24"},
1263 | {file = "lxml-4.6.3-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:820628b7b3135403540202e60551e741f9b6d3304371712521be939470b454ec"},
1264 | {file = "lxml-4.6.3-cp36-cp36m-manylinux2014_x86_64.whl", hash = "sha256:c47ff7e0a36d4efac9fd692cfa33fbd0636674c102e9e8d9b26e1b93a94e7617"},
1265 | {file = "lxml-4.6.3-cp36-cp36m-win32.whl", hash = "sha256:5a0a14e264069c03e46f926be0d8919f4105c1623d620e7ec0e612a2e9bf1c04"},
1266 | {file = "lxml-4.6.3-cp36-cp36m-win_amd64.whl", hash = "sha256:92e821e43ad382332eade6812e298dc9701c75fe289f2a2d39c7960b43d1e92a"},
1267 | {file = "lxml-4.6.3-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:efd7a09678fd8b53117f6bae4fa3825e0a22b03ef0a932e070c0bdbb3a35e654"},
1268 | {file = "lxml-4.6.3-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:efac139c3f0bf4f0939f9375af4b02c5ad83a622de52d6dfa8e438e8e01d0eb0"},
1269 | {file = "lxml-4.6.3-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:0fbcf5565ac01dff87cbfc0ff323515c823081c5777a9fc7703ff58388c258c3"},
1270 | {file = "lxml-4.6.3-cp37-cp37m-manylinux2010_i686.whl", hash = "sha256:36108c73739985979bf302006527cf8a20515ce444ba916281d1c43938b8bb96"},
1271 | {file = "lxml-4.6.3-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:122fba10466c7bd4178b07dba427aa516286b846b2cbd6f6169141917283aae2"},
1272 | {file = "lxml-4.6.3-cp37-cp37m-manylinux2014_x86_64.whl", hash = "sha256:cdaf11d2bd275bf391b5308f86731e5194a21af45fbaaaf1d9e8147b9160ea92"},
1273 | {file = "lxml-4.6.3-cp37-cp37m-win32.whl", hash = "sha256:3439c71103ef0e904ea0a1901611863e51f50b5cd5e8654a151740fde5e1cade"},
1274 | {file = "lxml-4.6.3-cp37-cp37m-win_amd64.whl", hash = "sha256:4289728b5e2000a4ad4ab8da6e1db2e093c63c08bdc0414799ee776a3f78da4b"},
1275 | {file = "lxml-4.6.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:b007cbb845b28db4fb8b6a5cdcbf65bacb16a8bd328b53cbc0698688a68e1caa"},
1276 | {file = "lxml-4.6.3-cp38-cp38-manylinux1_i686.whl", hash = "sha256:76fa7b1362d19f8fbd3e75fe2fb7c79359b0af8747e6f7141c338f0bee2f871a"},
1277 | {file = "lxml-4.6.3-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:26e761ab5b07adf5f555ee82fb4bfc35bf93750499c6c7614bd64d12aaa67927"},
1278 | {file = "lxml-4.6.3-cp38-cp38-manylinux2010_i686.whl", hash = "sha256:e1cbd3f19a61e27e011e02f9600837b921ac661f0c40560eefb366e4e4fb275e"},
1279 | {file = "lxml-4.6.3-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:66e575c62792c3f9ca47cb8b6fab9e35bab91360c783d1606f758761810c9791"},
1280 | {file = "lxml-4.6.3-cp38-cp38-manylinux2014_x86_64.whl", hash = "sha256:1b38116b6e628118dea5b2186ee6820ab138dbb1e24a13e478490c7db2f326ae"},
1281 | {file = "lxml-4.6.3-cp38-cp38-win32.whl", hash = "sha256:89b8b22a5ff72d89d48d0e62abb14340d9e99fd637d046c27b8b257a01ffbe28"},
1282 | {file = "lxml-4.6.3-cp38-cp38-win_amd64.whl", hash = "sha256:2a9d50e69aac3ebee695424f7dbd7b8c6d6eb7de2a2eb6b0f6c7db6aa41e02b7"},
1283 | {file = "lxml-4.6.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:ce256aaa50f6cc9a649c51be3cd4ff142d67295bfc4f490c9134d0f9f6d58ef0"},
1284 | {file = "lxml-4.6.3-cp39-cp39-manylinux1_i686.whl", hash = "sha256:7610b8c31688f0b1be0ef882889817939490a36d0ee880ea562a4e1399c447a1"},
1285 | {file = "lxml-4.6.3-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:f8380c03e45cf09f8557bdaa41e1fa7c81f3ae22828e1db470ab2a6c96d8bc23"},
1286 | {file = "lxml-4.6.3-cp39-cp39-manylinux2010_i686.whl", hash = "sha256:3082c518be8e97324390614dacd041bb1358c882d77108ca1957ba47738d9d59"},
1287 | {file = "lxml-4.6.3-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:884ab9b29feaca361f7f88d811b1eea9bfca36cf3da27768d28ad45c3ee6f969"},
1288 | {file = "lxml-4.6.3-cp39-cp39-manylinux2014_x86_64.whl", hash = "sha256:6f12e1427285008fd32a6025e38e977d44d6382cf28e7201ed10d6c1698d2a9a"},
1289 | {file = "lxml-4.6.3-cp39-cp39-win32.whl", hash = "sha256:33bb934a044cf32157c12bfcfbb6649807da20aa92c062ef51903415c704704f"},
1290 | {file = "lxml-4.6.3-cp39-cp39-win_amd64.whl", hash = "sha256:542d454665a3e277f76954418124d67516c5f88e51a900365ed54a9806122b83"},
1291 | {file = "lxml-4.6.3.tar.gz", hash = "sha256:39b78571b3b30645ac77b95f7c69d1bffc4cf8c3b157c435a34da72e78c82468"},
1292 | ]
1293 | markdown = [
1294 | {file = "Markdown-3.3.4-py3-none-any.whl", hash = "sha256:96c3ba1261de2f7547b46a00ea8463832c921d3f9d6aba3f255a6f71386db20c"},
1295 | {file = "Markdown-3.3.4.tar.gz", hash = "sha256:31b5b491868dcc87d6c24b7e3d19a0d730d59d3e46f4eea6430a321bed387a49"},
1296 | ]
1297 | markupsafe = [
1298 | {file = "MarkupSafe-2.0.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:f9081981fe268bd86831e5c75f7de206ef275defcb82bc70740ae6dc507aee51"},
1299 | {file = "MarkupSafe-2.0.1-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:0955295dd5eec6cb6cc2fe1698f4c6d84af2e92de33fbcac4111913cd100a6ff"},
1300 | {file = "MarkupSafe-2.0.1-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:0446679737af14f45767963a1a9ef7620189912317d095f2d9ffa183a4d25d2b"},
1301 | {file = "MarkupSafe-2.0.1-cp36-cp36m-manylinux2010_i686.whl", hash = "sha256:f826e31d18b516f653fe296d967d700fddad5901ae07c622bb3705955e1faa94"},
1302 | {file = "MarkupSafe-2.0.1-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:fa130dd50c57d53368c9d59395cb5526eda596d3ffe36666cd81a44d56e48872"},
1303 | {file = "MarkupSafe-2.0.1-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:905fec760bd2fa1388bb5b489ee8ee5f7291d692638ea5f67982d968366bef9f"},
1304 | {file = "MarkupSafe-2.0.1-cp36-cp36m-win32.whl", hash = "sha256:6c4ca60fa24e85fe25b912b01e62cb969d69a23a5d5867682dd3e80b5b02581d"},
1305 | {file = "MarkupSafe-2.0.1-cp36-cp36m-win_amd64.whl", hash = "sha256:b2f4bf27480f5e5e8ce285a8c8fd176c0b03e93dcc6646477d4630e83440c6a9"},
1306 | {file = "MarkupSafe-2.0.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:0717a7390a68be14b8c793ba258e075c6f4ca819f15edfc2a3a027c823718567"},
1307 | {file = "MarkupSafe-2.0.1-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:6557b31b5e2c9ddf0de32a691f2312a32f77cd7681d8af66c2692efdbef84c18"},
1308 | {file = "MarkupSafe-2.0.1-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:49e3ceeabbfb9d66c3aef5af3a60cc43b85c33df25ce03d0031a608b0a8b2e3f"},
1309 | {file = "MarkupSafe-2.0.1-cp37-cp37m-manylinux2010_i686.whl", hash = "sha256:d7f9850398e85aba693bb640262d3611788b1f29a79f0c93c565694658f4071f"},
1310 | {file = "MarkupSafe-2.0.1-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:6a7fae0dd14cf60ad5ff42baa2e95727c3d81ded453457771d02b7d2b3f9c0c2"},
1311 | {file = "MarkupSafe-2.0.1-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:b7f2d075102dc8c794cbde1947378051c4e5180d52d276987b8d28a3bd58c17d"},
1312 | {file = "MarkupSafe-2.0.1-cp37-cp37m-win32.whl", hash = "sha256:a30e67a65b53ea0a5e62fe23682cfe22712e01f453b95233b25502f7c61cb415"},
1313 | {file = "MarkupSafe-2.0.1-cp37-cp37m-win_amd64.whl", hash = "sha256:611d1ad9a4288cf3e3c16014564df047fe08410e628f89805e475368bd304914"},
1314 | {file = "MarkupSafe-2.0.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:be98f628055368795d818ebf93da628541e10b75b41c559fdf36d104c5787066"},
1315 | {file = "MarkupSafe-2.0.1-cp38-cp38-manylinux1_i686.whl", hash = "sha256:1d609f577dc6e1aa17d746f8bd3c31aa4d258f4070d61b2aa5c4166c1539de35"},
1316 | {file = "MarkupSafe-2.0.1-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:7d91275b0245b1da4d4cfa07e0faedd5b0812efc15b702576d103293e252af1b"},
1317 | {file = "MarkupSafe-2.0.1-cp38-cp38-manylinux2010_i686.whl", hash = "sha256:01a9b8ea66f1658938f65b93a85ebe8bc016e6769611be228d797c9d998dd298"},
1318 | {file = "MarkupSafe-2.0.1-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:47ab1e7b91c098ab893b828deafa1203de86d0bc6ab587b160f78fe6c4011f75"},
1319 | {file = "MarkupSafe-2.0.1-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:97383d78eb34da7e1fa37dd273c20ad4320929af65d156e35a5e2d89566d9dfb"},
1320 | {file = "MarkupSafe-2.0.1-cp38-cp38-win32.whl", hash = "sha256:023cb26ec21ece8dc3907c0e8320058b2e0cb3c55cf9564da612bc325bed5e64"},
1321 | {file = "MarkupSafe-2.0.1-cp38-cp38-win_amd64.whl", hash = "sha256:984d76483eb32f1bcb536dc27e4ad56bba4baa70be32fa87152832cdd9db0833"},
1322 | {file = "MarkupSafe-2.0.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:2ef54abee730b502252bcdf31b10dacb0a416229b72c18b19e24a4509f273d26"},
1323 | {file = "MarkupSafe-2.0.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:3c112550557578c26af18a1ccc9e090bfe03832ae994343cfdacd287db6a6ae7"},
1324 | {file = "MarkupSafe-2.0.1-cp39-cp39-manylinux1_i686.whl", hash = "sha256:53edb4da6925ad13c07b6d26c2a852bd81e364f95301c66e930ab2aef5b5ddd8"},
1325 | {file = "MarkupSafe-2.0.1-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:f5653a225f31e113b152e56f154ccbe59eeb1c7487b39b9d9f9cdb58e6c79dc5"},
1326 | {file = "MarkupSafe-2.0.1-cp39-cp39-manylinux2010_i686.whl", hash = "sha256:4efca8f86c54b22348a5467704e3fec767b2db12fc39c6d963168ab1d3fc9135"},
1327 | {file = "MarkupSafe-2.0.1-cp39-cp39-manylinux2010_x86_64.whl", hash = "sha256:ab3ef638ace319fa26553db0624c4699e31a28bb2a835c5faca8f8acf6a5a902"},
1328 | {file = "MarkupSafe-2.0.1-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:f8ba0e8349a38d3001fae7eadded3f6606f0da5d748ee53cc1dab1d6527b9509"},
1329 | {file = "MarkupSafe-2.0.1-cp39-cp39-win32.whl", hash = "sha256:10f82115e21dc0dfec9ab5c0223652f7197feb168c940f3ef61563fc2d6beb74"},
1330 | {file = "MarkupSafe-2.0.1-cp39-cp39-win_amd64.whl", hash = "sha256:693ce3f9e70a6cf7d2fb9e6c9d8b204b6b39897a2c4a1aa65728d5ac97dcc1d8"},
1331 | {file = "MarkupSafe-2.0.1.tar.gz", hash = "sha256:594c67807fb16238b30c44bdf74f36c02cdf22d1c8cda91ef8a0ed8dabf5620a"},
1332 | ]
1333 | mergedeep = [
1334 | {file = "mergedeep-1.3.4-py3-none-any.whl", hash = "sha256:70775750742b25c0d8f36c55aed03d24c3384d17c951b3175d898bd778ef0307"},
1335 | {file = "mergedeep-1.3.4.tar.gz", hash = "sha256:0096d52e9dad9939c3d975a774666af186eda617e6ca84df4c94dec30004f2a8"},
1336 | ]
1337 | mkdocs = [
1338 | {file = "mkdocs-1.2.1-py3-none-any.whl", hash = "sha256:11141126e5896dd9d279b3e4814eb488e409a0990fb638856255020406a8e2e7"},
1339 | {file = "mkdocs-1.2.1.tar.gz", hash = "sha256:6e0ea175366e3a50d334597b0bc042b8cebd512398cdd3f6f34842d0ef524905"},
1340 | ]
1341 | mkdocs-material = [
1342 | {file = "mkdocs-material-7.1.9.tar.gz", hash = "sha256:5a2fd487f769f382a7c979e869e4eab1372af58d7dec44c4365dd97ef5268cb5"},
1343 | {file = "mkdocs_material-7.1.9-py2.py3-none-any.whl", hash = "sha256:92c8a2bd3bd44d5948eefc46ba138e2d3285cac658900112b6bf5722c7d067a5"},
1344 | ]
1345 | mkdocs-material-extensions = [
1346 | {file = "mkdocs-material-extensions-1.0.1.tar.gz", hash = "sha256:6947fb7f5e4291e3c61405bad3539d81e0b3cd62ae0d66ced018128af509c68f"},
1347 | {file = "mkdocs_material_extensions-1.0.1-py3-none-any.whl", hash = "sha256:d90c807a88348aa6d1805657ec5c0b2d8d609c110e62b9dce4daf7fa981fa338"},
1348 | ]
1349 | nodeenv = [
1350 | {file = "nodeenv-1.6.0-py2.py3-none-any.whl", hash = "sha256:621e6b7076565ddcacd2db0294c0381e01fd28945ab36bcf00f41c5daf63bef7"},
1351 | {file = "nodeenv-1.6.0.tar.gz", hash = "sha256:3ef13ff90291ba2a4a7a4ff9a979b63ffdd00a464dbe04acf0ea6471517a4c2b"},
1352 | ]
1353 | packaging = [
1354 | {file = "packaging-20.9-py2.py3-none-any.whl", hash = "sha256:67714da7f7bc052e064859c05c595155bd1ee9f69f76557e21f051443c20947a"},
1355 | {file = "packaging-20.9.tar.gz", hash = "sha256:5b327ac1320dc863dca72f4514ecc086f31186744b84a230374cc1fd776feae5"},
1356 | ]
1357 | parse = [
1358 | {file = "parse-1.19.0.tar.gz", hash = "sha256:9ff82852bcb65d139813e2a5197627a94966245c897796760a3a2a8eb66f020b"},
1359 | ]
1360 | pep8 = [
1361 | {file = "pep8-1.7.1-py2.py3-none-any.whl", hash = "sha256:b22cfae5db09833bb9bd7c8463b53e1a9c9b39f12e304a8d0bba729c501827ee"},
1362 | {file = "pep8-1.7.1.tar.gz", hash = "sha256:fe249b52e20498e59e0b5c5256aa52ee99fc295b26ec9eaa85776ffdb9fe6374"},
1363 | ]
1364 | pluggy = [
1365 | {file = "pluggy-0.13.1-py2.py3-none-any.whl", hash = "sha256:966c145cd83c96502c3c3868f50408687b38434af77734af1e9ca461a4081d2d"},
1366 | {file = "pluggy-0.13.1.tar.gz", hash = "sha256:15b2acde666561e1298d71b523007ed7364de07029219b604cf808bfa1c765b0"},
1367 | ]
1368 | pre-commit = [
1369 | {file = "pre_commit-2.13.0-py2.py3-none-any.whl", hash = "sha256:b679d0fddd5b9d6d98783ae5f10fd0c4c59954f375b70a58cbe1ce9bcf9809a4"},
1370 | {file = "pre_commit-2.13.0.tar.gz", hash = "sha256:764972c60693dc668ba8e86eb29654ec3144501310f7198742a767bec385a378"},
1371 | ]
1372 | py = [
1373 | {file = "py-1.10.0-py2.py3-none-any.whl", hash = "sha256:3b80836aa6d1feeaa108e046da6423ab8f6ceda6468545ae8d02d9d58d18818a"},
1374 | {file = "py-1.10.0.tar.gz", hash = "sha256:21b81bda15b66ef5e1a777a21c4dcd9c20ad3efd0b3f817e7a809035269e1bd3"},
1375 | ]
1376 | pygments = [
1377 | {file = "Pygments-2.9.0-py3-none-any.whl", hash = "sha256:d66e804411278594d764fc69ec36ec13d9ae9147193a1740cd34d272ca383b8e"},
1378 | {file = "Pygments-2.9.0.tar.gz", hash = "sha256:a18f47b506a429f6f4b9df81bb02beab9ca21d0a5fee38ed15aef65f0545519f"},
1379 | ]
1380 | pymdown-extensions = [
1381 | {file = "pymdown-extensions-8.2.tar.gz", hash = "sha256:b6daa94aad9e1310f9c64c8b1f01e4ce82937ab7eb53bfc92876a97aca02a6f4"},
1382 | {file = "pymdown_extensions-8.2-py3-none-any.whl", hash = "sha256:141452d8ed61165518f2c923454bf054866b85cf466feedb0eb68f04acdc2560"},
1383 | ]
1384 | pyparsing = [
1385 | {file = "pyparsing-2.4.7-py2.py3-none-any.whl", hash = "sha256:ef9d7589ef3c200abe66653d3f1ab1033c3c419ae9b9bdb1240a85b024efc88b"},
1386 | {file = "pyparsing-2.4.7.tar.gz", hash = "sha256:c203ec8783bf771a155b207279b9bccb8dea02d8f0c9e5f8ead507bc3246ecc1"},
1387 | ]
1388 | pytest = [
1389 | {file = "pytest-6.2.4-py3-none-any.whl", hash = "sha256:91ef2131a9bd6be8f76f1f08eac5c5317221d6ad1e143ae03894b862e8976890"},
1390 | {file = "pytest-6.2.4.tar.gz", hash = "sha256:50bcad0a0b9c5a72c8e4e7c9855a3ad496ca6a881a3641b4260605450772c54b"},
1391 | ]
1392 | pytest-cache = [
1393 | {file = "pytest-cache-1.0.tar.gz", hash = "sha256:be7468edd4d3d83f1e844959fd6e3fd28e77a481440a7118d430130ea31b07a9"},
1394 | ]
1395 | pytest-cov = [
1396 | {file = "pytest-cov-2.12.1.tar.gz", hash = "sha256:261ceeb8c227b726249b376b8526b600f38667ee314f910353fa318caa01f4d7"},
1397 | {file = "pytest_cov-2.12.1-py2.py3-none-any.whl", hash = "sha256:261bb9e47e65bd099c89c3edf92972865210c36813f80ede5277dceb77a4a62a"},
1398 | ]
1399 | pytest-pep8 = [
1400 | {file = "pytest-pep8-1.0.6.tar.gz", hash = "sha256:032ef7e5fa3ac30f4458c73e05bb67b0f036a8a5cb418a534b3170f89f120318"},
1401 | ]
1402 | python-dateutil = [
1403 | {file = "python-dateutil-2.8.1.tar.gz", hash = "sha256:73ebfe9dbf22e832286dafa60473e4cd239f8592f699aa5adaf10050e6e1823c"},
1404 | {file = "python_dateutil-2.8.1-py2.py3-none-any.whl", hash = "sha256:75bb3f31ea686f1197762692a9ee6a7550b59fc6ca3a1f4b5d7e32fb98e2da2a"},
1405 | ]
1406 | pyyaml = [
1407 | {file = "PyYAML-5.4.1-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:3b2b1824fe7112845700f815ff6a489360226a5609b96ec2190a45e62a9fc922"},
1408 | {file = "PyYAML-5.4.1-cp27-cp27m-win32.whl", hash = "sha256:129def1b7c1bf22faffd67b8f3724645203b79d8f4cc81f674654d9902cb4393"},
1409 | {file = "PyYAML-5.4.1-cp27-cp27m-win_amd64.whl", hash = "sha256:4465124ef1b18d9ace298060f4eccc64b0850899ac4ac53294547536533800c8"},
1410 | {file = "PyYAML-5.4.1-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:bb4191dfc9306777bc594117aee052446b3fa88737cd13b7188d0e7aa8162185"},
1411 | {file = "PyYAML-5.4.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:6c78645d400265a062508ae399b60b8c167bf003db364ecb26dcab2bda048253"},
1412 | {file = "PyYAML-5.4.1-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:4e0583d24c881e14342eaf4ec5fbc97f934b999a6828693a99157fde912540cc"},
1413 | {file = "PyYAML-5.4.1-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:72a01f726a9c7851ca9bfad6fd09ca4e090a023c00945ea05ba1638c09dc3347"},
1414 | {file = "PyYAML-5.4.1-cp36-cp36m-manylinux2014_s390x.whl", hash = "sha256:895f61ef02e8fed38159bb70f7e100e00f471eae2bc838cd0f4ebb21e28f8541"},
1415 | {file = "PyYAML-5.4.1-cp36-cp36m-win32.whl", hash = "sha256:3bd0e463264cf257d1ffd2e40223b197271046d09dadf73a0fe82b9c1fc385a5"},
1416 | {file = "PyYAML-5.4.1-cp36-cp36m-win_amd64.whl", hash = "sha256:e4fac90784481d221a8e4b1162afa7c47ed953be40d31ab4629ae917510051df"},
1417 | {file = "PyYAML-5.4.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:5accb17103e43963b80e6f837831f38d314a0495500067cb25afab2e8d7a4018"},
1418 | {file = "PyYAML-5.4.1-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:e1d4970ea66be07ae37a3c2e48b5ec63f7ba6804bdddfdbd3cfd954d25a82e63"},
1419 | {file = "PyYAML-5.4.1-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:cb333c16912324fd5f769fff6bc5de372e9e7a202247b48870bc251ed40239aa"},
1420 | {file = "PyYAML-5.4.1-cp37-cp37m-manylinux2014_s390x.whl", hash = "sha256:fe69978f3f768926cfa37b867e3843918e012cf83f680806599ddce33c2c68b0"},
1421 | {file = "PyYAML-5.4.1-cp37-cp37m-win32.whl", hash = "sha256:dd5de0646207f053eb0d6c74ae45ba98c3395a571a2891858e87df7c9b9bd51b"},
1422 | {file = "PyYAML-5.4.1-cp37-cp37m-win_amd64.whl", hash = "sha256:08682f6b72c722394747bddaf0aa62277e02557c0fd1c42cb853016a38f8dedf"},
1423 | {file = "PyYAML-5.4.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:d2d9808ea7b4af864f35ea216be506ecec180628aced0704e34aca0b040ffe46"},
1424 | {file = "PyYAML-5.4.1-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:8c1be557ee92a20f184922c7b6424e8ab6691788e6d86137c5d93c1a6ec1b8fb"},
1425 | {file = "PyYAML-5.4.1-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:fd7f6999a8070df521b6384004ef42833b9bd62cfee11a09bda1079b4b704247"},
1426 | {file = "PyYAML-5.4.1-cp38-cp38-manylinux2014_s390x.whl", hash = "sha256:bfb51918d4ff3d77c1c856a9699f8492c612cde32fd3bcd344af9be34999bfdc"},
1427 | {file = "PyYAML-5.4.1-cp38-cp38-win32.whl", hash = "sha256:fa5ae20527d8e831e8230cbffd9f8fe952815b2b7dae6ffec25318803a7528fc"},
1428 | {file = "PyYAML-5.4.1-cp38-cp38-win_amd64.whl", hash = "sha256:0f5f5786c0e09baddcd8b4b45f20a7b5d61a7e7e99846e3c799b05c7c53fa696"},
1429 | {file = "PyYAML-5.4.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:294db365efa064d00b8d1ef65d8ea2c3426ac366c0c4368d930bf1c5fb497f77"},
1430 | {file = "PyYAML-5.4.1-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:74c1485f7707cf707a7aef42ef6322b8f97921bd89be2ab6317fd782c2d53183"},
1431 | {file = "PyYAML-5.4.1-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:d483ad4e639292c90170eb6f7783ad19490e7a8defb3e46f97dfe4bacae89122"},
1432 | {file = "PyYAML-5.4.1-cp39-cp39-manylinux2014_s390x.whl", hash = "sha256:fdc842473cd33f45ff6bce46aea678a54e3d21f1b61a7750ce3c498eedfe25d6"},
1433 | {file = "PyYAML-5.4.1-cp39-cp39-win32.whl", hash = "sha256:49d4cdd9065b9b6e206d0595fee27a96b5dd22618e7520c33204a4a3239d5b10"},
1434 | {file = "PyYAML-5.4.1-cp39-cp39-win_amd64.whl", hash = "sha256:c20cfa2d49991c8b4147af39859b167664f2ad4561704ee74c1de03318e898db"},
1435 | {file = "PyYAML-5.4.1.tar.gz", hash = "sha256:607774cbba28732bfa802b54baa7484215f530991055bb562efbed5b2f20a45e"},
1436 | ]
1437 | pyyaml-env-tag = [
1438 | {file = "pyyaml_env_tag-0.1-py3-none-any.whl", hash = "sha256:af31106dec8a4d68c60207c1886031cbf839b68aa7abccdb19868200532c2069"},
1439 | {file = "pyyaml_env_tag-0.1.tar.gz", hash = "sha256:70092675bda14fdec33b31ba77e7543de9ddc88f2e5b99160396572d11525bdb"},
1440 | ]
1441 | requests = [
1442 | {file = "requests-2.25.1-py2.py3-none-any.whl", hash = "sha256:c210084e36a42ae6b9219e00e48287def368a26d03a048ddad7bfee44f75871e"},
1443 | {file = "requests-2.25.1.tar.gz", hash = "sha256:27973dd4a904a4f13b263a19c866c13b92a39ed1c964655f025f3f8d3d75b804"},
1444 | ]
1445 | selenium = [
1446 | {file = "selenium-3.141.0-py2.py3-none-any.whl", hash = "sha256:2d7131d7bc5a5b99a2d9b04aaf2612c411b03b8ca1b1ee8d3de5845a9be2cb3c"},
1447 | {file = "selenium-3.141.0.tar.gz", hash = "sha256:deaf32b60ad91a4611b98d8002757f29e6f2c2d5fcaf202e1c9ad06d6772300d"},
1448 | ]
1449 | six = [
1450 | {file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"},
1451 | {file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"},
1452 | ]
1453 | soupsieve = [
1454 | {file = "soupsieve-2.2.1-py3-none-any.whl", hash = "sha256:c2c1c2d44f158cdbddab7824a9af8c4f83c76b1e23e049479aa432feb6c4c23b"},
1455 | {file = "soupsieve-2.2.1.tar.gz", hash = "sha256:052774848f448cf19c7e959adf5566904d525f33a3f8b6ba6f6f8f26ec7de0cc"},
1456 | ]
1457 | toml = [
1458 | {file = "toml-0.10.2-py2.py3-none-any.whl", hash = "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b"},
1459 | {file = "toml-0.10.2.tar.gz", hash = "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f"},
1460 | ]
1461 | ujson = [
1462 | {file = "ujson-4.0.2-cp36-cp36m-macosx_10_14_x86_64.whl", hash = "sha256:e390df0dcc7897ffb98e17eae1f4c442c39c91814c298ad84d935a3c5c7a32fa"},
1463 | {file = "ujson-4.0.2-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:84b1dca0d53b0a8d58835f72ea2894e4d6cf7a5dd8f520ab4cbd698c81e49737"},
1464 | {file = "ujson-4.0.2-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:91396a585ba51f84dc71c8da60cdc86de6b60ba0272c389b6482020a1fac9394"},
1465 | {file = "ujson-4.0.2-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:eb6b25a7670c7537a5998e695fa62ff13c7f9c33faf82927adf4daa460d5f62e"},
1466 | {file = "ujson-4.0.2-cp36-cp36m-win_amd64.whl", hash = "sha256:f8aded54c2bc554ce20b397f72101737dd61ee7b81c771684a7dd7805e6cca0c"},
1467 | {file = "ujson-4.0.2-cp37-cp37m-macosx_10_14_x86_64.whl", hash = "sha256:30962467c36ff6de6161d784cd2a6aac1097f0128b522d6e9291678e34fb2b47"},
1468 | {file = "ujson-4.0.2-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:fc51e545d65689c398161f07fd405104956ec27f22453de85898fa088b2cd4bb"},
1469 | {file = "ujson-4.0.2-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:e6e90330670c78e727d6637bb5a215d3e093d8e3570d439fd4922942f88da361"},
1470 | {file = "ujson-4.0.2-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:5e1636b94c7f1f59a8ead4c8a7bab1b12cc52d4c21ababa295ffec56b445fd2a"},
1471 | {file = "ujson-4.0.2-cp37-cp37m-win_amd64.whl", hash = "sha256:e2cadeb0ddc98e3963bea266cc5b884e5d77d73adf807f0bda9eca64d1c509d5"},
1472 | {file = "ujson-4.0.2-cp38-cp38-macosx_10_14_x86_64.whl", hash = "sha256:a214ba5a21dad71a43c0f5aef917cd56a2d70bc974d845be211c66b6742a471c"},
1473 | {file = "ujson-4.0.2-cp38-cp38-manylinux1_i686.whl", hash = "sha256:0190d26c0e990c17ad072ec8593647218fe1c675d11089cd3d1440175b568967"},
1474 | {file = "ujson-4.0.2-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:f273a875c0b42c2a019c337631bc1907f6fdfbc84210cc0d1fff0e2019bbfaec"},
1475 | {file = "ujson-4.0.2-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:d3a87888c40b5bfcf69b4030427cd666893e826e82cc8608d1ba8b4b5e04ea99"},
1476 | {file = "ujson-4.0.2-cp38-cp38-win_amd64.whl", hash = "sha256:7333e8bc45ea28c74ae26157eacaed5e5629dbada32e0103c23eb368f93af108"},
1477 | {file = "ujson-4.0.2-cp39-cp39-macosx_10_14_x86_64.whl", hash = "sha256:b3a6dcc660220539aa718bcc9dbd6dedf2a01d19c875d1033f028f212e36d6bb"},
1478 | {file = "ujson-4.0.2-cp39-cp39-manylinux1_i686.whl", hash = "sha256:0ea07fe57f9157118ca689e7f6db72759395b99121c0ff038d2e38649c626fb1"},
1479 | {file = "ujson-4.0.2-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:4d6d061563470cac889c0a9fd367013a5dbd8efc36ad01ab3e67a57e56cad720"},
1480 | {file = "ujson-4.0.2-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:b5c70704962cf93ec6ea3271a47d952b75ae1980d6c56b8496cec2a722075939"},
1481 | {file = "ujson-4.0.2-cp39-cp39-win_amd64.whl", hash = "sha256:aad6d92f4d71e37ea70e966500f1951ecd065edca3a70d3861b37b176dd6702c"},
1482 | {file = "ujson-4.0.2.tar.gz", hash = "sha256:c615a9e9e378a7383b756b7e7a73c38b22aeb8967a8bfbffd4741f7ffd043c4d"},
1483 | ]
1484 | urllib3 = [
1485 | {file = "urllib3-1.26.6-py2.py3-none-any.whl", hash = "sha256:39fb8672126159acb139a7718dd10806104dec1e2f0f6c88aab05d17df10c8d4"},
1486 | {file = "urllib3-1.26.6.tar.gz", hash = "sha256:f57b4c16c62fa2760b7e3d97c35b255512fb6b59a259730f36ba32ce9f8e342f"},
1487 | ]
1488 | virtualenv = [
1489 | {file = "virtualenv-20.4.7-py2.py3-none-any.whl", hash = "sha256:2b0126166ea7c9c3661f5b8e06773d28f83322de7a3ff7d06f0aed18c9de6a76"},
1490 | {file = "virtualenv-20.4.7.tar.gz", hash = "sha256:14fdf849f80dbb29a4eb6caa9875d476ee2a5cf76a5f5415fa2f1606010ab467"},
1491 | ]
1492 | waitress = [
1493 | {file = "waitress-2.0.0-py3-none-any.whl", hash = "sha256:29af5a53e9fb4e158f525367678b50053808ca6c21ba585754c77d790008c746"},
1494 | {file = "waitress-2.0.0.tar.gz", hash = "sha256:69e1f242c7f80273490d3403c3976f3ac3b26e289856936d1f620ed48f321897"},
1495 | ]
1496 | watchdog = [
1497 | {file = "watchdog-2.1.3-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:9628f3f85375a17614a2ab5eac7665f7f7be8b6b0a2a228e6f6a2e91dd4bfe26"},
1498 | {file = "watchdog-2.1.3-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:acc4e2d5be6f140f02ee8590e51c002829e2c33ee199036fcd61311d558d89f4"},
1499 | {file = "watchdog-2.1.3-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:85b851237cf3533fabbc034ffcd84d0fa52014b3121454e5f8b86974b531560c"},
1500 | {file = "watchdog-2.1.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:a12539ecf2478a94e4ba4d13476bb2c7a2e0a2080af2bb37df84d88b1b01358a"},
1501 | {file = "watchdog-2.1.3-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:6fe9c8533e955c6589cfea6f3f0a1a95fb16867a211125236c82e1815932b5d7"},
1502 | {file = "watchdog-2.1.3-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:d9456f0433845e7153b102fffeb767bde2406b76042f2216838af3b21707894e"},
1503 | {file = "watchdog-2.1.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:fd8c595d5a93abd441ee7c5bb3ff0d7170e79031520d113d6f401d0cf49d7c8f"},
1504 | {file = "watchdog-2.1.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:0bcfe904c7d404eb6905f7106c54873503b442e8e918cc226e1828f498bdc0ca"},
1505 | {file = "watchdog-2.1.3-pp36-pypy36_pp73-macosx_10_9_x86_64.whl", hash = "sha256:bf84bd94cbaad8f6b9cbaeef43080920f4cb0e61ad90af7106b3de402f5fe127"},
1506 | {file = "watchdog-2.1.3-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:b8ddb2c9f92e0c686ea77341dcb58216fa5ff7d5f992c7278ee8a392a06e86bb"},
1507 | {file = "watchdog-2.1.3-py3-none-manylinux2014_aarch64.whl", hash = "sha256:8805a5f468862daf1e4f4447b0ccf3acaff626eaa57fbb46d7960d1cf09f2e6d"},
1508 | {file = "watchdog-2.1.3-py3-none-manylinux2014_armv7l.whl", hash = "sha256:3e305ea2757f81d8ebd8559d1a944ed83e3ab1bdf68bcf16ec851b97c08dc035"},
1509 | {file = "watchdog-2.1.3-py3-none-manylinux2014_i686.whl", hash = "sha256:431a3ea70b20962e6dee65f0eeecd768cd3085ea613ccb9b53c8969de9f6ebd2"},
1510 | {file = "watchdog-2.1.3-py3-none-manylinux2014_ppc64.whl", hash = "sha256:e4929ac2aaa2e4f1a30a36751160be391911da463a8799460340901517298b13"},
1511 | {file = "watchdog-2.1.3-py3-none-manylinux2014_ppc64le.whl", hash = "sha256:201cadf0b8c11922f54ec97482f95b2aafca429c4c3a4bb869a14f3c20c32686"},
1512 | {file = "watchdog-2.1.3-py3-none-manylinux2014_s390x.whl", hash = "sha256:3a7d242a7963174684206093846537220ee37ba9986b824a326a8bb4ef329a33"},
1513 | {file = "watchdog-2.1.3-py3-none-manylinux2014_x86_64.whl", hash = "sha256:54e057727dd18bd01a3060dbf5104eb5a495ca26316487e0f32a394fd5fe725a"},
1514 | {file = "watchdog-2.1.3-py3-none-win32.whl", hash = "sha256:b5fc5c127bad6983eecf1ad117ab3418949f18af9c8758bd10158be3647298a9"},
1515 | {file = "watchdog-2.1.3-py3-none-win_amd64.whl", hash = "sha256:44acad6f642996a2b50bb9ce4fb3730dde08f23e79e20cd3d8e2a2076b730381"},
1516 | {file = "watchdog-2.1.3-py3-none-win_ia64.whl", hash = "sha256:0bcdf7b99b56a3ae069866c33d247c9994ffde91b620eaf0306b27e099bd1ae0"},
1517 | {file = "watchdog-2.1.3.tar.gz", hash = "sha256:e5236a8e8602ab6db4b873664c2d356c365ab3cac96fbdec4970ad616415dd45"},
1518 | ]
1519 | webob = [
1520 | {file = "WebOb-1.8.7-py2.py3-none-any.whl", hash = "sha256:73aae30359291c14fa3b956f8b5ca31960e420c28c1bec002547fb04928cf89b"},
1521 | {file = "WebOb-1.8.7.tar.gz", hash = "sha256:b64ef5141be559cfade448f044fa45c2260351edcb6a8ef6b7e00c7dcef0c323"},
1522 | ]
1523 | webtest = [
1524 | {file = "WebTest-2.0.35-py2.py3-none-any.whl", hash = "sha256:44ddfe99b5eca4cf07675e7222c81dd624d22f9a26035d2b93dc8862dc1153c6"},
1525 | {file = "WebTest-2.0.35.tar.gz", hash = "sha256:aac168b5b2b4f200af4e35867cf316712210e3d5db81c1cbdff38722647bb087"},
1526 | ]
1527 | werkzeug = [
1528 | {file = "Werkzeug-2.0.1-py3-none-any.whl", hash = "sha256:6c1ec500dcdba0baa27600f6a22f6333d8b662d22027ff9f6202e3367413caa8"},
1529 | {file = "Werkzeug-2.0.1.tar.gz", hash = "sha256:1de1db30d010ff1af14a009224ec49ab2329ad2cde454c8a708130642d579c42"},
1530 | ]
1531 | zipp = [
1532 | {file = "zipp-3.4.1-py3-none-any.whl", hash = "sha256:51cb66cc54621609dd593d1787f286ee42a5c0adbb4b29abea5a63edc3e03098"},
1533 | {file = "zipp-3.4.1.tar.gz", hash = "sha256:3607921face881ba3e026887d8150cca609d517579abe052ac81fc5aeffdbd76"},
1534 | ]
1535 |
--------------------------------------------------------------------------------
/pyproject.toml:
--------------------------------------------------------------------------------
1 | [tool.poetry]
2 | name = "toapi"
3 | version = "2.1.3"
4 | description = "Every web site provides APIs."
5 | authors = ["Elliot Gao "]
6 | license = "MIT"
7 |
8 | [tool.poetry.dependencies]
9 | python = "^3.8"
10 | colorama = "^0.4.4"
11 | cchardet = "^2.1.7"
12 | htmlparsing = "^0.1.5"
13 | requests = "^2.25.1"
14 | htmlfetcher = "^0.0.6"
15 | flask = "^2.0.1"
16 | click = "^8.0.1"
17 | cssselect = "^1.1.0"
18 |
19 | [tool.poetry.dev-dependencies]
20 | pytest = "^6.2.4"
21 | mkdocs = "^1.2.1"
22 | pytest-pep8 = "^1.0.6"
23 | pytest-cov = "^2.12.1"
24 | webtest = "^2.0.35"
25 | codecov = "^2.1.11"
26 | mkdocs-material = "^7.1.9"
27 | ujson = "^4.0.2"
28 | pre-commit = "^2.13.0"
29 |
30 | [build-system]
31 | requires = ["poetry-core>=1.0.0"]
32 | build-backend = "poetry.core.masonry.api"
33 |
34 | [tool.poetry.scripts]
35 | toapi="toapi.cli:cli"
36 |
37 | [tool.poetry.urls]
38 | "homepage" = "https://github.com/gaojiuli/toapi"
39 | "repository" = "https://github.com/gaojiuli/toapi"
40 | "documentation" = "https://gaojiuli.github.io/toapi/"
41 |
42 |
43 |
44 | [tool.black]
45 | line-length = 79
46 | include = '\.pyi?$'
47 | exclude = '''
48 | /(
49 | \.git
50 | | \.hg
51 | | \.mypy_cache
52 | | \.tox
53 | | \.venv
54 | | _build
55 | | buck-out
56 | | build
57 | | dist
58 | )/
59 | '''
60 |
61 | [tool.isort]
62 | line_length = 79
63 | use_parentheses = true
64 | include_trailing_comma = true
65 | multi_line_output = 3
66 | force_grid_wrap = 0
67 | no_lines_before = "LOCALFOLDER"
68 |
--------------------------------------------------------------------------------
/tests/test_toapi.py:
--------------------------------------------------------------------------------
1 | import pytest
2 | from flask import request
3 | from htmlparsing import Attr, Text
4 | from webtest import TestApp as App
5 |
6 | from toapi import Api, Item
7 | from toapi.cli import cli
8 |
9 |
10 | def test_api():
11 | api = Api()
12 |
13 | @api.site("https://news.ycombinator.com")
14 | @api.list(".athing")
15 | @api.route("/posts?page={page}", "/news?p={page}")
16 | @api.route("/posts", "/news?p=1")
17 | class Post(Item):
18 | url = Attr(".storylink", "href")
19 | title = Text(".storylink")
20 |
21 | @api.site("https://news.ycombinator.com")
22 | @api.route("/posts?page={page}", "/news?p={page}")
23 | @api.route("/posts", "/news?p=1")
24 | class Page(Item):
25 | next_page = Attr(".morelink", "href")
26 |
27 | def clean_next_page(self, value):
28 | return api.convert_string(
29 | "/" + value,
30 | "/news?p={page}",
31 | request.host_url.strip("/") + "/posts?page={page}",
32 | )
33 |
34 | app = App(api.app)
35 | with pytest.raises(SystemExit):
36 | api.run(port=-1)
37 | app.get("/posts?page=1")
38 | app.get("/posts?page=1")
39 | print(cli.__dict__)
40 |
41 |
42 | def test_error():
43 | api = Api()
44 |
45 | @api.site("https://news.ycombinator.com")
46 | @api.list(".athing")
47 | @api.route("/posts?page={page}", "/news?p={page}")
48 | @api.route("/posts", "/news?p=1")
49 | class Post(Item):
50 | url = Attr(".storylink", "no this attribute")
51 | title = Text(".storylink")
52 |
53 | app = App(api.app)
54 | with pytest.raises(Exception):
55 | app.get("/posts?page=1")
56 |
--------------------------------------------------------------------------------
/toapi/__init__.py:
--------------------------------------------------------------------------------
1 | from toapi.api import Api
2 | from toapi.item import Item
3 |
--------------------------------------------------------------------------------
/toapi/api.py:
--------------------------------------------------------------------------------
1 | import logging
2 | import traceback
3 | from collections import defaultdict
4 | from time import time
5 |
6 | import cchardet
7 | import requests
8 | from colorama import Fore
9 | from flask import Flask, jsonify, request
10 | from htmlfetcher import HTMLFetcher
11 | from parse import parse
12 |
13 | from toapi.log import logger
14 |
15 |
16 | class Api:
17 | def __init__(self, site: str = "", browser: str = None) -> None:
18 | self.app: Flask = Flask(__name__)
19 | self.browser = browser and HTMLFetcher(browser=browser)
20 | self._site = site.strip("/")
21 | self._routes: list = []
22 | self._cache = defaultdict(dict)
23 | self._storage = defaultdict(str)
24 | self.__init_server()
25 |
26 | def __init_server(self) -> None:
27 | self.app.logger.setLevel(logging.ERROR)
28 |
29 | @self.app.route("/")
30 | def handler(path):
31 | try:
32 | start_time = time()
33 | full_path = request.full_path.strip("?")
34 | results = self.parse_url(full_path)
35 | end_time = time()
36 | time_usage = end_time - start_time
37 | res = jsonify(results)
38 | logger.info(
39 | Fore.GREEN,
40 | "Received",
41 | "%s %s 200 %.2fms"
42 | % (request.url, len(res.response), time_usage * 1000),
43 | )
44 | return res
45 | except Exception as e:
46 | logger.error("Serving", f"{e}")
47 | logger.error("Serving", "%s" % str(traceback.format_exc()))
48 | return jsonify({"msg": "System Error", "code": -1}), 500
49 |
50 | def run(self, host="127.0.0.1", port=5000, **options):
51 | try:
52 | logger.info(Fore.GREEN, "Serving", f"http://{host}:{port}")
53 | self.app.run(host, port, **options)
54 | except Exception as e:
55 | logger.error("Serving", "%s" % str(e))
56 | logger.error("Serving", "%s" % str(traceback.format_exc()))
57 | exit()
58 |
59 | def absolute_url(self, base_url, url: str) -> str:
60 | return "{}/{}".format(base_url, url.lstrip("/"))
61 |
62 | def convert_string(self, source_string, source_format, target_format):
63 | parsed_words = parse(source_format, source_string)
64 | if parsed_words is not None:
65 | target_string = target_format.format(**parsed_words.named)
66 | return target_string
67 | return None
68 |
69 | def parse_url(self, full_path: str) -> dict:
70 | results = self._cache.get(full_path)
71 | if results is not None:
72 | logger.info(Fore.YELLOW, "Cache", f"Get<{full_path}>")
73 | return results
74 |
75 | results = {}
76 | for source_format, target_format, item in self._routes:
77 | parsed_path = self.convert_string(
78 | full_path, source_format, target_format
79 | )
80 | if parsed_path is not None:
81 | full_url = self.absolute_url(item._site, parsed_path)
82 | html = self.fetch(full_url)
83 | result = item.parse(html)
84 | logger.info(
85 | Fore.CYAN,
86 | "Parsed",
87 | f"Item<{item.__name__}[{len(result)}]>",
88 | )
89 | results.update({item.__name__: result})
90 |
91 | self._cache[full_path] = results
92 | logger.info(Fore.YELLOW, "Cache", f"Set<{full_path}>")
93 |
94 | return results
95 |
96 | def fetch(self, url: str) -> str:
97 | html = self._storage.get(url)
98 | if html is not None:
99 | logger.info(Fore.BLUE, "Storage", f"Get<{url}>")
100 | return html
101 | if self.browser is not None:
102 | html = self.browser.get(url)
103 | else:
104 | r = requests.get(url)
105 | content = r.content
106 | charset = cchardet.detect(content)
107 | html = content.decode(charset["encoding"] or "utf-8")
108 | logger.info(Fore.GREEN, "Sent", f"{url} {len(html)}")
109 | self._storage[url] = html
110 | logger.info(Fore.BLUE, "Storage", f"Set<{url}>")
111 | return html
112 |
113 | def route(self, source_format: str, target_format: str) -> callable:
114 | def fn(item):
115 | self._routes.append([source_format, target_format, item])
116 | logger.info(
117 | Fore.GREEN,
118 | "Register",
119 | f"<{item.__name__}: {source_format} {target_format}>",
120 | )
121 |
122 | return item
123 |
124 | return fn
125 |
126 | def list(self, selector: str) -> callable:
127 | def fn(item):
128 | item._list = True
129 | item._selector = selector
130 | return item
131 |
132 | return fn
133 |
134 | def site(self, site: str) -> callable:
135 | def fn(item):
136 | item._site = site or self._site
137 | item._site = item._site.strip("/")
138 | return item
139 |
140 | return fn
141 |
--------------------------------------------------------------------------------
/toapi/cli.py:
--------------------------------------------------------------------------------
1 | import click
2 |
3 | from toapi import __version__
4 |
5 |
6 | @click.group(context_settings={"help_option_names": ["-h", "--help"]})
7 | @click.version_option(__version__, "-v", "--version")
8 | def cli():
9 | """
10 | Toapi - Every web site provides APIs.
11 | """
12 |
--------------------------------------------------------------------------------
/toapi/item.py:
--------------------------------------------------------------------------------
1 | from collections import OrderedDict
2 |
3 | from htmlparsing import HTMLParsing, Selector
4 |
5 |
6 | class ItemType(type):
7 | def __new__(cls, what, bases=None, attrdict=None):
8 | __fields__ = OrderedDict()
9 |
10 | for name, selector in attrdict.items():
11 | if isinstance(selector, Selector):
12 | __fields__[name] = selector
13 |
14 | for name in __fields__.keys():
15 | del attrdict[name]
16 |
17 | instance = type.__new__(cls, what, bases, attrdict)
18 | instance._list = None
19 | instance._site = None
20 | instance._selector = None
21 | instance.__fields__ = __fields__
22 | return instance
23 |
24 |
25 | class Item(metaclass=ItemType):
26 | @classmethod
27 | def parse(cls, html: str):
28 | if cls._list:
29 | result = HTMLParsing(html).list(cls._selector, cls.__fields__)
30 | result = [cls._clean(item) for item in result]
31 | else:
32 | result = HTMLParsing(html).detail(cls.__fields__)
33 | result = cls._clean(result)
34 | return result
35 |
36 | @classmethod
37 | def _clean(cls, item):
38 | for name, selector in cls.__fields__.items():
39 | clean_method = getattr(cls, "clean_%s" % name, None)
40 | if clean_method is not None:
41 | item[name] = clean_method(cls, item[name])
42 | return item
43 |
--------------------------------------------------------------------------------
/toapi/log.py:
--------------------------------------------------------------------------------
1 | """
2 | logger.info(Fore.GREEN, 'Sent', 'https://fuck.com/path1 1231 200')
3 | logger.info(Fore.GREEN, 'Received', 'http://127.0.0.1/path2 231 200')
4 | logger.info(Fore.YELLOW, 'Cache', 'Set')
5 | logger.info(Fore.BLUE, 'Storage', 'Get')
6 | logger.info(Fore.CYAN, 'Parsed', 'Item')
7 | logger.error('Cache', 'Set')
8 | logger.error('Storage', 'Get')
9 | logger.error('Parse', 'Item')
10 | """
11 | import logging
12 |
13 | import colorama
14 | from colorama import Fore, Style
15 |
16 | colorama.init(autoreset=True)
17 |
18 |
19 | class Logger:
20 | def __init__(self, name, level=logging.DEBUG):
21 | logging.basicConfig(
22 | format="%(asctime)s %(message)-10s ", datefmt="%Y/%m/%d %H:%M:%S"
23 | )
24 |
25 | self.logger = logging.getLogger(name)
26 | self.logger.setLevel(level)
27 |
28 | def info(self, color, type, message):
29 | self.logger.info(
30 | color + "[%-8s] %-2s %s" % (type, "OK", message) + Style.RESET_ALL
31 | )
32 |
33 | def error(self, type, message):
34 | self.logger.error(
35 | Fore.RED
36 | + "[%-8s] %-4s %s" % (type, "FAIL", message)
37 | + Style.RESET_ALL
38 | )
39 |
40 |
41 | logger = Logger(__name__)
42 |
--------------------------------------------------------------------------------