commit 3848c62530323bf2fd9081f659a6f0479c83f421 Author: Krazybug Date: Tue Mar 1 00:38:17 2022 +0100 First public version diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..dcd5e70 --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +*.pyc +calishot.egg-info/ +.vscode +.DS_Store +output/ \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..9fc5c87 --- /dev/null +++ b/README.md @@ -0,0 +1,108 @@ +# CALISHOT Guidelines + +## Installation + +You need poetry pre installed. +Clone the repository then : + + +``` +poetry install +poetry shell +mkdir output +cd output +``` +Then create a list.txt file with all your calibre urls + +## Indexing + +``` +python ../calishot import list.txt + +python ../calishot check + +sqlite-utils sites.db 'select url from sites where status="online" ' | jq -r '.[].url' > online.txt + +python ../calishot index-site-list online.txt + +python ../calishot build-index --english +mv index.db index-eng.db + +python ../calishot build-index --noenglish +mv index.db index-non-eng.db + +# for diplaying global size and total count of formats +python ../calishot get-stats + +python ../calishot index-to-json | jq -r '. | {title: .title.label, authors, year, language, publisher, series, desc: .title.href, tags, identifiers, formats, format_links: [.links[].href]}' > calibre.json + +sqlite-utils index.db 'select uuid, title, authors, year, series, language, formats, publisher, tags, identifiers from summary where instr(formats, "mp3") >0 order by uuid limit 101' + + + +``` +## Deployment + +1. Install poetry, datasette and it's plugins + +``` +poetry new calishot +poetry shell +poetry add datasette +poetry add datasette-json-html +poetry add datasette-pretty-json +``` + +You can eventually install it with virtualenv/pip if you don't want to use poetry: + +``` +python -m venv calishot +. ./calishot/bin/activate +pip install datasette +pip install datasette-json-html +pip install datasette-pretty-json +```` + + +2. Prepare the calishot settings: + +Download the sqlite db file to the same directory and then + + +``` +cat < metadata.json +{ + "databases": { + "index": { + "tables": { + "summary": { + "sort": "title", + "searchmode": "raw" + } + } + } + } + } +EOF +``` + +You can now run a local test: + +``` +datasette serve index-non-eng.db --config sql_time_limit_ms:50000 --config allow_download:off --config max_returned_rows:2000 --config num_sql_threads:10 --config allow_csv_stream:off --metadata metadata.json +``` + +Open your browser to http://localhost:8001/ and check the result. + +3. Now you're ready to publish :) + +Install [heroku-cli](https://devcenter.heroku.com/articles/heroku-cli) then : + +export NODE_EXTRA_CA_CERTS=/calishot/CAall.cer + +``` +heroku login -i + + +datasette publish heroku index-non-eng.db -n calishot-non-eng-1 --install=datasette-json-html --install=datasette-pretty-json --extra-options="--config sql_time_limit_ms:50000 --config allow_download:off --config num_sql_threads:10 --config max_returned_rows:500 --config allow_csv_stream:off" --metadata metadata.json +``` diff --git a/calishot/__init__.py b/calishot/__init__.py new file mode 100644 index 0000000..b794fd4 --- /dev/null +++ b/calishot/__init__.py @@ -0,0 +1 @@ +__version__ = '0.1.0' diff --git a/calishot/__main__.py b/calishot/__main__.py new file mode 100644 index 0000000..129aa9b --- /dev/null +++ b/calishot/__main__.py @@ -0,0 +1,19 @@ +import fire + +from site_index import import_urls_from_file, check_calibre_list, check_calibre_site +from calistat import index_site_list, get_stats, index_site_list_seq +from ebooks_index import build_index, index_to_json +from diff import diff + +if __name__ == "__main__": + fire.Fire({ + "import": import_urls_from_file, + "check":check_calibre_list, + "check-site":check_calibre_site, + "index-site-list": index_site_list, + "index-site-list-seq": index_site_list_seq, + "build-index": build_index, + "get-stats": get_stats, + "index-to-json": index_to_json, + "diff": diff + }) diff --git a/calishot/calistat.py b/calishot/calistat.py new file mode 100644 index 0000000..77c7621 --- /dev/null +++ b/calishot/calistat.py @@ -0,0 +1,514 @@ +import sys +import os +import time +import re +import shutil +from typing import Dict +import requests +import json +from humanize import naturalsize as hsize +import humanize +from langid.langid import LanguageIdentifier, model +import iso639 +import time +import json +import unidecode + +from requests.adapters import HTTPAdapter +import urllib.parse +import urllib3 +from pathlib import Path +import uuid +from sqlite_utils import Database + +import gevent +from gevent import monkey +from gevent import Timeout +from gevent.pool import Pool +monkey.patch_socket() +# monkey.patch_all() +import fire + +from site_index import init_sites_db, get_libs_from_site + +urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning) +identifier = LanguageIdentifier.from_modelstring(model, norm_probs=True) + +def get_site_db(uuid, dir): + f_uuid=str(uuid)+".db" + print(f_uuid) + path = Path(dir) / str(f_uuid) + return Database(path) + + + +def init_site_db(site, _uuid="", dir="."): + + if not _uuid: + s_uuid=str(uuid.uuid4()) + else: + s_uuid=str(_uuid) + + f_uuid=s_uuid+".db" + path = Path(dir) / f_uuid + db = Database(path) + + + if not "site" in db.table_names(): + s=db["site"] + s.insert( + { + "uuid": s_uuid, + "urls": [site], + "version": "", + "major": 0, + "schema_version": 1, + } + , pk='uuid' + ) + + + if not "ebooks" in db.table_names(): + db["ebooks"].create({ + "uuid": str, + "id": int, + "library": str, #TODO: manage libraries ids as integer to prevent library renam on remote site + "title": str, + "authors": str, + "series": str, + "series_index": int, + # "edition": int, + "language": str, + "desc": str, + "identifiers": str, + "tags": str, + "publisher": str, + "pubdate": str, + "last_modified": str, + "timestamp": str, + "formats": str, + "cover": int, + # "epub": int, + # "mobi": int, + # "pdf": int, + # TODO: add the most common formats to avoid alter tables + }, pk="uuid") + + if not "libraries" in db.table_names(): + db["libraries"].create({ + "id": int, + "names": str + }, pk="id") + + + # db.table("ebooks", pk="id") + # db.table("ebooks", pk="id", alter=True + + return db + + +def get_format_url(db, book, format): + url = json.loads(list(db['site'].rows)[0]["urls"])[0] + library=book['library'] + id_=str(book['id']) + + f_url = url+"/get/"+format+"/"+id_+"/"+library + return f_url + + + +def get_desc_url(db, book): + url = json.loads(list(db['site'].rows)[0]["urls"])[0] + + library=book['library'] + id_=str(book['id']) + + f_urls=[] + + major= list(db['site'].rows)[0]["major"] + + if major >= 3: + d_url =url+"#book_id="+id_+"&library_id="+library+"&panel=book_details" + else: + d_url =url+"/browse/book/"+id_ + + return d_url + + +def save_books_metadata_from_site(db, books): + uuid = list(db['site'].rows)[0]["uuid"] + + # print(uuid) + + ebooks_t=db["ebooks"] + + + # print([c[1] for c in ebooks_t.columns]) + # for b in books: + # print(b['title']) + # ebooks_t.insert(b, alter=True) + + # ebooks_t.insert_all(books, alter=True) + ebooks_t.insert_all(books, alter=True, pk='uuid', batch_size=1000) + # print([c[1] for c in ebooks_t.columns]) + +def load_metadata(dir, uuid): + pass + +def update_done_status(book): + source=book['source'] + if source['status']!='ignored': + if set(source['formats'].keys()) == set(book['formats']) & set(source['formats'].keys()): + book['source']['status']="done" + else: + book['source']['status']="todo" + +def index_site_list_seq(file): + with open(file) as f: + for s in f.readlines(): + # try: + # index_ebooks(s.rstrip()) + # except: + # continue + index_ebooks(s.rstrip()) + +def index_site_list(file): + pool = Pool(40) + + with open(file) as f: + sites = f.readlines() + sites= [s.rstrip() for s in sites] + print(sites) + pool.map(index_ebooks_except, sites) + +def index_ebooks_except(site): + try: + index_ebooks(site) + except: + print("Error on site") + +def index_ebooks(site, library="", start=0, stop=0, dir=".", num=1000, force_refresh=False): + + #TODO old calibres don't manage libraries. /ajax/library-info endpoint doesn't exist. It would be better to manage calibre version directly + + libs=[] + try: + libs= get_libs_from_site(site) + except: + print("old lib") + + _uuid=str(uuid.uuid4()) + + if libs: + for lib in libs: + index_ebooks_from_library(site=site, _uuid=_uuid, library=lib, start=start, stop=stop, dir=dir, num=num, force_refresh=force_refresh) + else: + index_ebooks_from_library(site=site, _uuid=_uuid, start=start, stop=stop, dir=dir, num=num, force_refresh=force_refresh) + +def index_ebooks_from_library(site, _uuid="", library="", start=0, stop=0, dir=".", num=1000, force_refresh=False): + + offset= 0 if not start else start-1 + num=min(1000, num) + server=site.rstrip('/') + api=server+'/ajax/' + lib=library + library= '/'+library if library else library + + timeout=15 + + print(f"\nIndexing library: {lib} from server: {server} ") + url=api+'search'+library+'?num=0' + print(f"\nGetting ebooks count of library: {lib} from server:{server} ") + # print(url) + + try: + r=requests.get(url, verify=False, timeout=(timeout, 30)) + r.raise_for_status() + except requests.RequestException as e: + print("Unable to open site:", url) + return + # pass + except Exception as e: + print ("Other issue:", e) + return + # pass + except : + print("Wazza !!!!") + sys.exit(1) + + + total_num=int(r.json()["total_num"]) + total_num= total_num if not stop else stop + print() + print(f"Total count={total_num} from {server}") + + # library=r.json()["base_url"].split('/')[-1] + # base_url=r.json()["base_url"] + + # cache_db=init_cache_db(dir=dir) + # _uuid=get_uuid_from_url(cache_db) + db=init_site_db(site, _uuid=_uuid, dir=dir) + r_site = (list(db['site'].rows)[0]) + + r_site['version']=r.headers['server'] + r_site['major']=int(re.search('calibre.(\d).*', r.headers['server']).group(1)) + db["site"].upsert(r_site, pk='uuid') + + print() + + range=offset+1 + while offset < total_num: + remaining_num = min(num, total_num - offset) + # print() + # print("Downloading ids: offset="+str(offset), "num="+str(remaining_num)) + print ('\r {:180.180}'.format(f'Downloading ids: offset={str(offset)} count={str(remaining_num)} from {server}'), end='') + + # url=server+base_url+'?num='+str(remaining_num)+'&offset='+str(offset)+'&sort=timestamp&sort_order=desc' + url=api+'search'+library+'?num='+str(remaining_num)+'&offset='+str(offset)+'&sort=timestamp&sort_order=desc' + + # print("->", url) + try: + r=requests.get(url, verify=False, timeout=(timeout, 30)) + r.raise_for_status() + except requests.RequestException as e: + print ("Connection issue:", e) + return + # pass + except Exception as e: + print ("Other issue:", e) + return + # pass + except : + print ("Wazza !!!!") + return + # print("Ids received from:"+str(offset), "to:"+str(offset+remaining_num-1)) + + # print() + # print("Downloading metadata from", str(offset+1), "to", str(offset+remaining_num)) + print ('\r {:180.180}'.format(f'Downloading metadata from {str(offset+1)} to {str(offset+remaining_num)}/{total_num} from {server}'), end='') + books_s=",".join(str(i) for i in r.json()['book_ids']) + url=api+'books'+library+'?ids='+books_s + # url=server+base_url+'/books?ids='+books_s + # print("->", url) + # print ('\r{:190.190}'.format(f'url= {url} ...'), end='') + + try: + r=requests.get(url, verify=False, timeout=(60, 60)) + r.raise_for_status() + except requests.RequestException as e: + print ("Connection issue:", e) + return + # pass + except Exception as e: + print ("Other issue:", e) + return + # pass + except : + print ("Wazza !!!!") + return + # print(len(r.json()), "received") + print ('\r {:180.180}'.format(f'{len(r.json())} received'), end='') + + + books=[] + for id, r_book in r.json().items(): + uuid=r_book['uuid'] + if not uuid: + print ("No uuid for ebook: ignored") + continue + + + if r_book['authors']: + desc= f"({r_book['title']} / {r_book['authors'][0]})" + else: + desc= f"({r_book['title']})" + + # print (f'\r--> {range}/{total_num} - {desc}', end='') + # print (f'\r{server}--> {range}/{total_num} - {desc}', end='') + print ('\r {:180.180} '.format(f'{range}/{total_num} ({server} : {uuid} --> {desc}'), end='') + + + if not force_refresh: + # print("Checking local metadata:", uuid) + try: + book = load_metadata(dir, uuid) + except: + print("Unable to get metadata from:", uuid) + range+=1 + continue + if book: + print("Metadata already present for:", uuid) + range+=1 + continue + + if not r_book['formats']: + # print("No format found for {}".format(r_book['uuid'])) + range+=1 + continue + + book={} + book['uuid']=r_book['uuid'] + book['id']=id + book['library']=lib + + # book['title']=r_book['title'] + book['title']=unidecode.unidecode(r_book['title']) + # book['authors']=r_book['authors'] + + if r_book['authors']: + book['authors']=[unidecode.unidecode(s) for s in r_book['authors']] + # book['desc']="" + + book['desc']=r_book['comments'] + + if r_book['series']: + book['series']=unidecode.unidecode(r_book['series']) + # book['series']=[unidecode.unidecode(s) for s in r_book['series']] + s_i=r_book['series_index'] + if (s_i): + book['series_index']=int(s_i) + + # book['edition']=0 + + book['identifiers']=r_book['identifiers'] + + # book['tags']=r_book['tags'] + if r_book['tags']: + book['tags']=[unidecode.unidecode(s) for s in r_book['tags']] + + book['publisher']=r_book['publisher'] + # book['publisher']=unidecode.unidecode(r_book['publisher']) + + book['pubdate']=r_book['pubdate'] + + if not r_book['languages']: + # if True: + text=r_book['title']+". " + if r_book['comments']: + text=r_book['comments'] + s_language, prob=identifier.classify(text) + if prob >= 0.85: + language = iso639.to_iso639_2(s_language) + book['language']=language + else: + book['language']='' + else: + book['language']=iso639.to_iso639_2(r_book['languages'][0]) + + if r_book['cover']: + book['cover']= True + else: + book['cover']= False + + book['last_modified']=r_book['last_modified'] + book['timestamp']=r_book['timestamp'] + + book['formats']=[] + formats=r_book['formats'] + for f in formats: + if 'size' in r_book['format_metadata'][f]: + size=int(r_book['format_metadata'][f]['size']) + else: + # print() + # print(f"Size not found for format '{f}' uuid={uuid}: skipped") + pass + #TODO query the size when the function to rebuild the full url is ready + # + # print("Trying to get size online: {}".format('url')) + # try: + # size=get_file_size(s['url']) + # except: + # print("Unable to access size for format '{}' : {} skipped".format(f, uuid)) + # continue + book[f]=(size) + book['formats'].append(f) + + if not book['formats']: + # if not c_format: + # print() + # print(f"No format found for {book['uuid']} id={book['id']} : skipped") + range+=1 + # continue + + + books.append(book) + range+=1 + + # print() + print("Saving metadata") + print ('\r {:180.180}'.format(f'Saving metadata from {server}'), end='') + + try: + save_books_metadata_from_site(db, books) + print('\r {:180.180}'.format(f'--> Saved {range-1}/{total_num} ebooks from {server}'), end='') + except BaseException as err: + print (err) + + print() + print() + + # try: + # save_metadata(db, books) + # except: + # print("Unable to save book metadata") + + offset=offset+num + + + + +def query(query_str="", dir="."): + dbs=[] + for path in os.listdir(dir): + db = Database(path) + # print (db["ebooks"].count) + # for row in db["site"].rows: + # print (f'{row["urls"]}: {db["ebooks"].count}') + # db["ebooks"].search(query_str) + # url=db['site'].get(1)['urls'][0] + url=db['site'].get(1) + print (url) + + for ebook in db["ebooks"].rows_where(query_str): + # print (f"{ebook['title']} ({ebook['uuid']})") + print (ebook) + + + +def get_stats(dir="."): + dbs=[] + size=0 + count=0 + for f in os.listdir(dir): + if not f.endswith(".db"): + continue + if f == "index.db": + continue + path = Path(dir) / f + dbs.append(Database(path)) + + for db in dbs: + for i, ebook in enumerate(db["ebooks"].rows): + uuid=ebook['uuid'] + title=ebook['title'] + formats=json.loads(ebook['formats']) + # print(formats) + for f in formats: + if f in ebook: + if ebook[f]: + size+=ebook[f] + count+=1 + # print (f'\r{count} {f} --> {uuid}: {title}', end ='') + # print (f'\r{count} : {uuid} --> {f}', end='') + print (f'\r{count} formats - ebook : {uuid}', end='') + + print() + print("Total count of formats:", humanize.intcomma(count)) + print("Total size:", hsize(size)) + + + print() + + +if __name__ == "__main__": + fire.Fire() \ No newline at end of file diff --git a/calishot/diff.py b/calishot/diff.py new file mode 100644 index 0000000..ab22086 --- /dev/null +++ b/calishot/diff.py @@ -0,0 +1,64 @@ +from pathlib import Path +from sqlite_utils import Database +from sqlite_utils.db import NotFoundError +import json + +def init_diff_db(dir="."): + + path = Path(dir) / "diff.db" + + db_diff = Database(path) + if not "summary" in db_diff.table_names(): + db_diff["summary"].create({ + "uuid": str, + "title": str, + # "cover": str, + # "source": str + "authors": str, + "year": str, + "series": str, + "language": str, + "links": str, + # "desc": str, + "publisher": str, + "tags": str, + "identifiers": str, + "formats": str, + "status": str, + "old_location":str + } + # ) + , pk="uuid") + + return db_diff + +def diff(old, new, dir=".", ): + path = Path(dir) / old + db_old = Database(path) + + path = Path(dir) / new + db_new = Database(path) + + path = Path(dir) / "diff.db" + db_diff =init_diff_db(dir) + + for i, n_book in enumerate(db_new["summary"].rows): + n_uuid = n_book['uuid'] + print(i, n_uuid) + try: + o_book = db_old["summary"].get(n_uuid) + # print(n_uuid, '=OK') + o_loc=json.loads(o_book['title'])['href'] + n_loc=json.loads(n_book['title'])['href'] + if o_loc != n_loc : + print(n_uuid, 'MOVED') + n_book["status"]="MOVED" + n_book["old_location"]=o_loc + n_book.pop ('cover', None) + db_diff["summary"].insert(n_book, pk='uuid') + + except NotFoundError: + # print(n_uuid, '=NOK') + n_book.pop ('cover', None) + n_book["status"]="NEW" + db_diff["summary"].insert(n_book, pk='uuid') diff --git a/calishot/ebooks_index.py b/calishot/ebooks_index.py new file mode 100644 index 0000000..2f9b7f7 --- /dev/null +++ b/calishot/ebooks_index.py @@ -0,0 +1,237 @@ +import os +import sys +import json +from pathlib import Path +from sqlite_utils import Database +from humanize import naturalsize as hsize + +from calistat import get_desc_url, get_format_url + + +def init_index_db(dir="."): + + path = Path(dir) / "index.db" + + db_index = Database(path) + if not "summary" in db_index.table_names(): + db_index["summary"].create({ + "uuid": str, + "cover": str, + "title": str, + # "source": str + "authors": str, + "year": str, + "series": str, + "language": str, + "links": str, + # "desc": str, + "publisher": str, + "tags": str, + "identifiers": str, + "formats": str + } + # ) + , pk="uuid") + + # db_index.table("index", pk="uuid") + # db_index.table("summary").enable_fts(["title"]) + # db_index["summary"].enable_fts(["title", "authors", "series", "uuid", "language", "identifiers", "tags", "publisher", "formats", "pubdate"]) + db_index["summary"].enable_fts(["title", "authors", "series", "language", "identifiers", "tags", "publisher", "formats", "year"]) + + return db_index + + +def get_img_url(db, book): + url = json.loads(list(db['site'].rows)[0]["urls"])[0] + + library=book['library'] + id_=str(book['id']) + + f_urls=[] + + major= list(db['site'].rows)[0]["major"] + + if major >= 3: + d_url =url+"/get/thumb/"+id_+"/"+library+ "?sz=600x800" + else: + # d_url =url+"/get/thumb/"+id_ + d_url =url+"/get/thumb_90_120/"+id_ + + return d_url + + +def build_index (dir='.', english=True): + + dbs=[] + for f in os.listdir(dir): + if not f.endswith(".db"): + continue + if f in ("index.db", "sites.db"): + continue + p = Path(dir) / f + print(f) + try: + db = Database(p.resolve()) + except: + print ("Pb with:", f) + dbs.append(db) + + db_index = init_index_db(dir=dir) + index_t=db_index["summary"] + + batch_size=10000 + count=0 + summaries=[] + + for db in dbs: + for i, ebook in enumerate(db["ebooks"].rows): + if english and not ebook['language'] or ebook['language'] != "eng": + continue + elif not english and ebook['language'] == "eng": + continue + + if ebook['authors']: + ebook['authors']=formats=json.loads(ebook['authors']) + # if ebook['series']: + # ebook['series']=formats=json.loads(ebook['series']) + if ebook['identifiers']: + ebook['identifiers']=formats=json.loads(ebook['identifiers']) + if ebook['tags']: + ebook['tags']=formats=json.loads(ebook['tags']) + ebook['formats']=formats=json.loads(ebook['formats']) + ebook['links']="" + summary = {k: v for k, v in ebook.items() if k in ("uuid","title", "authors", "series", "language", "formats", "tags", "publisher", "identifiers")} + # summary = {k: v for k, v in ebook.items() if k in ("uuid","title", "authors", "series", "identifiers", "language", "tags", "publisher", "formats")} + summary['title']={'href': get_desc_url(db, ebook), 'label': ebook['title']} + + summary["cover"]= {"img_src": get_img_url(db, ebook), "width": 90} + + formats=[] + for f in ebook['formats']: + formats.append({'href': get_format_url(db, ebook, f), 'label': f"{f} ({hsize(ebook[f])})"}) + summary['links']=formats + + pubdate=ebook['pubdate'] + summary['year']=pubdate[0:4] if pubdate else "" + summaries.append(summary) + # print(summary) + count+=1 + print (f"\r{count} - ebook handled: {ebook['uuid']}", end='') + + if not count % batch_size: + # print() + # print(f"Saving summary by batch: {len(summaries)}") + # print(summaries) + # index_t.upsert_all(summaries, batch_size=1000, pk='uuid') + # index_t.insert_all(summaries, batch_size=1000, pk='uuid') + try: + index_t.insert_all(summaries, batch_size=batch_size) + except Exception as e: + # dump = [(s['uuid'],s['links']) for s in summaries] + # print(dump) + print() + print("UUID collisions. Probalbly a site duplicate") + print(e) + print() + + # index_t.upsert_all(summaries, batch_size=batch_size, pk='uuid') + # TODO Some ebooks could be missed. We need to compute the batch list, insert new ebooks and update the site index + + # print("Saved") + # print() + summaries=[] + + # print() + # print("saving summary") + # index_t.upsert_all(summaries, batch_size=1000, pk='uuid') + # index_t.insert_all(summaries, batch_size=1000, pk='uuid') + try: + index_t.insert_all(summaries, batch_size=batch_size) + except: + print("sqlite3.IntegrityError: UNIQUE constraint failed: summary.uuid") + + # print("summary done") + # print() + + print() + print("fts") + index_t.populate_fts(["title", "authors", "series", "identifiers", "language", "tags", "publisher", "formats", "year"]) + print("fts done") + + +def search(query_str, dir=".", links_only=False): + path = Path(dir) / "index.db" + db_index = Database(path) + # table=db_index["summary"] + # rows=table.search(query_str) + # print(rows) + sites=set() + ebook_ids=[] + for ebook in db_index["summary"].search(query_str): + sites.add(ebook[-1]) + ebook_ids.append((ebook[3], ebook[-1])) + # print (ebook) + # print("sites:", sites) + # print("ebooks:", ebook_ids) + + site_dbs={} + for s in sites: + f_uuid=s+".db" + path = Path(dir) / f_uuid + site_dbs[s]=Database(path) + # print(site_dbs[s].tables) + + for e in ebook_ids: + # ebook=site_dbs[e[1]]["ebooks"].get(e[0]) + # print("ebook:", ebook) + db=site_dbs[e[1]] + # ebooks=db.conn.execute("select * from ebooks").fetchone() + ebook=db.conn.execute(f'select * from ebooks where uuid="{e[0]}"').fetchone() + url=json.loads(db['site'].get(1)['urls'])[0] + library=db['site'].get(1)['library'] + formats=json.loads(ebook[14]) + id_=str(ebook[0]) + + if not links_only: + print() + print("Title:", ebook[2]) + print("Author:", ebook[3]) + print("Serie:", ebook[4]) + print("Formats:", formats) + + for f in formats: + print(url+"get/"+f+"/"+id_+"/"+library) + + +# https://stackoverflow.com/questions/26692284/how-to-prevent-brokenpipeerror-when-doing-a-flush-in-python + +def index_to_json(dir='.'): + path = Path(dir) / "index.db" + db = Database(path) + + # sys.stdout.flush() + + try: + for row in db["summary"].rows: + if row['title']: + row['title']=json.loads(row['title']) + if row['authors']: + row['authors']=json.loads(row['authors']) + if row['series']: + row['series']=json.loads(row['series']) + if row['links']: + row['links']=json.loads(row['links']) + if row['tags']: + row['tags']=json.loads(row['tags']) + if row['identifiers']: + row['identifiers']=json.loads(row['identifiers']) + if row['formats']: + row['formats']=json.loads(row['formats']) + + json.dump(row, sys.stdout) + sys.stdout.flush() + # return + except BrokenPipeError: + devnull = os.open(os.devnull, os.O_WRONLY) + os.dup2(devnull, sys.stdout.fileno()) + sys.exit(1) diff --git a/calishot/site_index.py b/calishot/site_index.py new file mode 100644 index 0000000..acd3586 --- /dev/null +++ b/calishot/site_index.py @@ -0,0 +1,206 @@ +import requests +from pathlib import Path +from urllib.parse import * +import uuid +from sqlite_utils import Database +import datetime + + +import gevent +from gevent import monkey +from gevent import Timeout +from gevent.pool import Pool +monkey.patch_socket() + + +def init_sites_db(dir="."): + + path = Path(dir) / "sites.db" + + db = Database(path) + if not "sites" in db.table_names(): + db["sites"].create({ + "uuid": str, + "url": str, + "hostnames": str, + "ports": str, + "country": int, + "isp": str, + "status": str, + "last_online": str, + "last_check": str, + "error": int, + # "schema_version": 1 + # # TODO: add the most common formats + }, pk="uuid") + # }, pk="uuid", not_null=True) + + # if not "sites" in db.table_names(): + # db["sites"].create({ + # "uuid": str + # }, pk="uuid",) + + db.table("sites", pk='uuid', batch_size=100, alter=True) + return db + + +def save_site(db: Database, site): + # # TODO: Check if the site is not alreday present + # def save_sites(db, sites): + # db["sites"].insert_all(sites, alter=True, batch_size=100) + if not 'uuid' in site: + site['uuid']=str(uuid.uuid4()) + print(site) + db["sites"].upsert(site, pk='uuid') + + +def check_and_save_site(db, site): + res= check_calibre_site(site) + print(res) + save_site(db, res) + +# import pysnooper +# @pysnooper.snoop() +def check_calibre_site(site): + ret={} + ret['uuid']=site["uuid"] + now=str(datetime.datetime.now()) + ret['last_check']=now + + api=site['url']+'/ajax/' + timeout=15 + library="" + url=api+'search'+library+'?num=0' + print() + print("Getting ebooks count:", site['url']) + print(url) + + try: + r=requests.get(url, verify=False, timeout=(timeout, 30)) + r.raise_for_status() + except requests.exceptions.HTTPError as e: + r.status_code + ret['error']=r.status_code + if (r.status_code == 401): + ret['status']="unauthorized" + else: + ret['status']="down" + return ret + except requests.RequestException as e: + print("Unable to open site:", url) + # print (getattr(e, 'message', repr(e))) + print (e) + ret['status']="down" + return ret + except Exception as e: + print ("Other issue:", e) + ret['status']='Unknown Error' + print (e) + return ret + except : + print("Wazza !!!!") + ret['status']='Critical Error' + print (e) + return ret + + try: + print("Total count=",r.json()["total_num"]) + except: + pass + + status=ret['status']='online' + if status=="online": + ret['last_online']=now + + return ret + + + +def get_site_uuid_from_url(db, url): + + site=urlparse(url) + hostname=site.hostname + site=site._replace(path='') + + url=urlunparse(site) + # print (url) + + # print (hostname) + row=db.conn.execute(f"select * from sites where instr(hostnames, '{hostname}')").fetchone() + # print(row) + if row: + return row + +def map_site_from_url(url): + ret={} + + site=urlparse(url) + + print(site) + site=site._replace(path='') + ret['url']=urlunparse(site) + ret['hostnames']=[site.hostname] + ret['ports']=[str(site.port)] + + return ret + + +def import_urls_from_file(filepath, dir='.'): + + #TODO skip malformed urls + #TODO use cache instead + + db=init_sites_db(dir) + + with open(filepath) as f: + for url in f.readlines(): + url=url.rstrip() + # url='http://'+url + if get_site_uuid_from_url(db, url): + print(f"'{url}'' already present") + continue + print(f"'{url}'' added") + save_site(db, map_site_from_url(url)) + + + +def get_libs_from_site(site): + + server=site.rstrip('/') + api=server+'/ajax/' + timeout=30 + + print() + print("Server:", server) + url=api+'library-info' + + print() + print("Getting libraries from", server) + # print(url) + + try: + r=requests.get(url, verify=False, timeout=(timeout, 30)) + r.raise_for_status() + except requests.RequestException as e: + print("Unable to open site:", url) + # return + except Exception as e: + print ("Other issue:", e) + return + # pass + + libraries = r.json()["library_map"].keys() + print("Libraries:", ", ".join(libraries)) + return libraries + +def check_calibre_list(dir='.'): + db=init_sites_db(dir) + sites=[] + for row in db["sites"].rows: + print(f"Queueing:{row['url']}") + sites.append(row) + print(sites) + pool = Pool(100) + pool.map(lambda s: check_and_save_site (db, s), sites) + +# example of a fts search sqlite-utils index.db "select * from summary_fts where summary_fts match 'title:fre*'" \ No newline at end of file diff --git a/poetry.lock b/poetry.lock new file mode 100644 index 0000000..265d0eb --- /dev/null +++ b/poetry.lock @@ -0,0 +1,1160 @@ +[[package]] +category = "main" +description = "File support for asyncio." +name = "aiofiles" +optional = false +python-versions = "*" +version = "0.5.0" + +[[package]] +category = "main" +description = "ASGI middleware for protecting against CSRF attacks" +name = "asgi-csrf" +optional = false +python-versions = "*" +version = "0.7.1" + +[package.dependencies] +itsdangerous = "*" +python-multipart = "*" + +[package.extras] +test = ["pytest", "pytest-asyncio", "httpx", "starlette", "pytest-cov", "asgi-lifespan"] + +[[package]] +category = "main" +description = "ASGI specs, helper code, and adapters" +name = "asgiref" +optional = false +python-versions = ">=3.5" +version = "3.2.10" + +[package.extras] +tests = ["pytest", "pytest-asyncio"] + +[[package]] +category = "dev" +description = "Atomic file writes." +marker = "sys_platform == \"win32\"" +name = "atomicwrites" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +version = "1.4.0" + +[[package]] +category = "dev" +description = "Classes Without Boilerplate" +name = "attrs" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +version = "20.3.0" + +[package.extras] +dev = ["coverage (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "zope.interface", "furo", "sphinx", "pre-commit"] +docs = ["furo", "sphinx", "zope.interface"] +tests = ["coverage (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "zope.interface"] +tests_no_zope = ["coverage (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six"] + +[[package]] +category = "main" +description = "Screen-scraping library" +name = "beautifulsoup4" +optional = false +python-versions = "*" +version = "4.9.3" + +[package.dependencies] +[package.dependencies.soupsieve] +python = ">=3.0" +version = ">1.2" + +[package.extras] +html5lib = ["html5lib"] +lxml = ["lxml"] + +[[package]] +category = "main" +description = "Dummy package for Beautiful Soup" +name = "bs4" +optional = false +python-versions = "*" +version = "0.0.1" + +[package.dependencies] +beautifulsoup4 = "*" + +[[package]] +category = "main" +description = "Python package for providing Mozilla's CA Bundle." +name = "certifi" +optional = false +python-versions = "*" +version = "2020.12.5" + +[[package]] +category = "main" +description = "Foreign Function Interface for Python calling C code." +marker = "platform_python_implementation == \"CPython\" and sys_platform == \"win32\"" +name = "cffi" +optional = false +python-versions = "*" +version = "1.14.4" + +[package.dependencies] +pycparser = "*" + +[[package]] +category = "main" +description = "Universal encoding detector for Python 2 and 3" +name = "chardet" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" +version = "4.0.0" + +[[package]] +category = "main" +description = "Composable command line interface toolkit" +name = "click" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" +version = "7.1.2" + +[[package]] +category = "main" +description = "Extends click.Group to invoke a command without explicit subcommand name" +name = "click-default-group" +optional = false +python-versions = "*" +version = "1.2.2" + +[package.dependencies] +click = "*" + +[[package]] +category = "dev" +description = "Cross-platform colored terminal text." +marker = "sys_platform == \"win32\"" +name = "colorama" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" +version = "0.4.4" + +[[package]] +category = "main" +description = "An open source multi-tool for exploring and publishing data" +name = "datasette" +optional = false +python-versions = "*" +version = "0.50.2" + +[package.dependencies] +Jinja2 = ">=2.10.3,<2.12.0" +PyYAML = ">=5.3,<6.0" +aiofiles = ">=0.4,<0.6" +asgi-csrf = ">=0.6" +asgiref = ">=3.2.10,<3.3.0" +click = ">=7.1.1,<7.2.0" +click-default-group = ">=1.2.2,<1.3.0" +httpx = ">=0.15" +hupper = ">=1.9,<2.0" +itsdangerous = ">=1.1,<2.0" +janus = ">=0.4,<0.6" +mergedeep = ">=1.1.1,<1.4.0" +pint = ">=0.9,<1.0" +pluggy = ">=0.13.0,<0.14.0" +python-baseconv = "1.2.2" +uvicorn = ">=0.11,<1.0" + +[package.extras] +docs = ["sphinx-rtd-theme", "sphinx-autobuild"] +test = ["pytest (>=5.2.2,<6.2.0)", "pytest-asyncio (>=0.10,<0.15)", "beautifulsoup4 (>=4.8.1,<4.10.0)", "black (20.8b1)", "pytest-timeout (>=1.4.2,<1.5)"] + +[[package]] +category = "main" +description = "Datasette plugin for rendering HTML based on JSON values" +name = "datasette-json-html" +optional = false +python-versions = "*" +version = "0.6.1" + +[package.dependencies] +datasette = "*" + +[package.extras] +test = ["pytest", "pytest-asyncio", "httpx"] + +[[package]] +category = "main" +description = "Datasette plugin that masks specified database columns" +name = "datasette-mask-columns" +optional = false +python-versions = "*" +version = "0.2" + +[package.dependencies] +datasette = ">=0.36,<1.0" + +[package.extras] +test = ["pytest", "pytest-asyncio", "httpx", "sqlite-utils"] + +[[package]] +category = "main" +description = "Datasette plugin that pretty-prints any column values that are valid JSON objects or arrays" +name = "datasette-pretty-json" +optional = false +python-versions = "*" +version = "0.2.1" + +[package.dependencies] +datasette = "*" + +[package.extras] +test = ["pytest"] + +[[package]] +category = "main" +description = "A library for automatically generating command line interfaces." +name = "fire" +optional = false +python-versions = "*" +version = "0.3.1" + +[package.dependencies] +six = "*" +termcolor = "*" + +[[package]] +category = "main" +description = "Coroutine-based network library" +name = "gevent" +optional = false +python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*" +version = "20.12.1" + +[package.dependencies] +cffi = ">=1.12.2" +greenlet = ">=0.4.17,<2.0" +setuptools = "*" +"zope.event" = "*" +"zope.interface" = "*" + +[package.extras] +dnspython = ["dnspython (>=1.16.0,<2.0)", "idna"] +docs = ["repoze.sphinx.autointerface", "sphinxcontrib-programoutput"] +monitor = ["psutil (>=5.7.0)"] +recommended = ["dnspython (>=1.16.0,<2.0)", "idna", "cffi (>=1.12.2)", "selectors2", "backports.socketpair", "psutil (>=5.7.0)"] +test = ["dnspython (>=1.16.0,<2.0)", "idna", "requests", "objgraph", "cffi (>=1.12.2)", "selectors2", "futures", "mock", "backports.socketpair", "contextvars (2.4)", "coverage (>=5.0)", "coveralls (>=1.7.0)", "psutil (>=5.7.0)"] + +[[package]] +category = "main" +description = "Lightweight in-process concurrent programming" +marker = "platform_python_implementation == \"CPython\"" +name = "greenlet" +optional = false +python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*" +version = "1.0.0" + +[package.extras] +docs = ["sphinx"] + +[[package]] +category = "main" +description = "A pure-Python, bring-your-own-I/O implementation of HTTP/1.1" +name = "h11" +optional = false +python-versions = ">=3.6" +version = "0.12.0" + +[[package]] +category = "main" +description = "A minimal low-level HTTP client." +name = "httpcore" +optional = false +python-versions = ">=3.6" +version = "0.12.2" + +[package.dependencies] +h11 = "<1.0.0" +sniffio = ">=1.0.0,<2.0.0" + +[package.extras] +http2 = ["h2 (>=3,<5)"] + +[[package]] +category = "main" +description = "The next generation HTTP client." +name = "httpx" +optional = false +python-versions = ">=3.6" +version = "0.16.1" + +[package.dependencies] +certifi = "*" +httpcore = ">=0.12.0,<0.13.0" +sniffio = "*" + +[package.dependencies.rfc3986] +extras = ["idna2008"] +version = ">=1.3,<2" + +[package.extras] +brotli = ["brotlipy (>=0.7.0,<0.8.0)"] +http2 = ["h2 (>=3.0.0,<4.0.0)"] + +[[package]] +category = "main" +description = "Python humanize utilities" +name = "humanize" +optional = false +python-versions = ">=3.5" +version = "2.6.0" + +[package.extras] +tests = ["freezegun", "pytest", "pytest-cov"] + +[[package]] +category = "main" +description = "Integrated process monitor for developing and reloading daemons." +name = "hupper" +optional = false +python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*" +version = "1.10.2" + +[package.extras] +docs = ["watchdog", "sphinx", "pylons-sphinx-themes"] +testing = ["watchdog", "pytest", "pytest-cov", "mock"] + +[[package]] +category = "main" +description = "Internationalized Domain Names in Applications (IDNA)" +name = "idna" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +version = "2.10" + +[[package]] +category = "main" +description = "ISO639-2 support for Python." +name = "iso639" +optional = false +python-versions = "*" +version = "0.1.4" + +[[package]] +category = "main" +description = "Various helpers to pass data to untrusted environments and back." +name = "itsdangerous" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +version = "1.1.0" + +[[package]] +category = "main" +description = "Mixed sync-async queue to interoperate between asyncio tasks and classic threads" +name = "janus" +optional = false +python-versions = ">=3.5.3" +version = "0.5.0" + +[[package]] +category = "main" +description = "A very fast and expressive template engine." +name = "jinja2" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" +version = "2.11.2" + +[package.dependencies] +MarkupSafe = ">=0.23" + +[package.extras] +i18n = ["Babel (>=0.8)"] + +[[package]] +category = "main" +description = "langid.py is a standalone Language Identification (LangID) tool." +name = "langid" +optional = false +python-versions = "*" +version = "1.1.6" + +[package.dependencies] +numpy = "*" + +[[package]] +category = "main" +description = "Safely add untrusted strings to HTML/XML markup." +name = "markupsafe" +optional = false +python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*" +version = "1.1.1" + +[[package]] +category = "main" +description = "A deep merge function for 🐍." +name = "mergedeep" +optional = false +python-versions = ">=3.6" +version = "1.3.1" + +[[package]] +category = "dev" +description = "More routines for operating on iterables, beyond itertools" +name = "more-itertools" +optional = false +python-versions = ">=3.5" +version = "8.6.0" + +[[package]] +category = "main" +description = "NumPy is the fundamental package for array computing with Python." +name = "numpy" +optional = false +python-versions = ">=3.6" +version = "1.19.5" + +[[package]] +category = "main" +description = "Core utilities for Python packages" +name = "packaging" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +version = "20.8" + +[package.dependencies] +pyparsing = ">=2.0.2" + +[[package]] +category = "main" +description = "Physical quantities module" +name = "pint" +optional = false +python-versions = ">=3.6" +version = "0.16.1" + +[package.dependencies] +packaging = "*" + +[package.extras] +numpy = ["numpy (>=1.14)"] +test = ["pytest", "pytest-mpl", "pytest-cov"] +uncertainties = ["uncertainties (>=3.0)"] + +[[package]] +category = "main" +description = "plugin and hook calling mechanisms for python" +name = "pluggy" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +version = "0.13.1" + +[package.extras] +dev = ["pre-commit", "tox"] + +[[package]] +category = "dev" +description = "library with cross-python path, ini-parsing, io, code, log facilities" +name = "py" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +version = "1.10.0" + +[[package]] +category = "main" +description = "C parser in Python" +marker = "platform_python_implementation == \"CPython\" and sys_platform == \"win32\"" +name = "pycparser" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +version = "2.20" + +[[package]] +category = "main" +description = "Python parsing module" +name = "pyparsing" +optional = false +python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" +version = "2.4.7" + +[[package]] +category = "dev" +description = "pytest: simple powerful testing with Python" +name = "pytest" +optional = false +python-versions = ">=3.5" +version = "5.4.3" + +[package.dependencies] +atomicwrites = ">=1.0" +attrs = ">=17.4.0" +colorama = "*" +more-itertools = ">=4.0.0" +packaging = "*" +pluggy = ">=0.12,<1.0" +py = ">=1.5.0" +wcwidth = "*" + +[package.extras] +checkqa-mypy = ["mypy (v0.761)"] +testing = ["argcomplete", "hypothesis (>=3.56)", "mock", "nose", "requests", "xmlschema"] + +[[package]] +category = "main" +description = "Convert numbers from base 10 integers to base X strings and back again." +name = "python-baseconv" +optional = false +python-versions = "*" +version = "1.2.2" + +[[package]] +category = "main" +description = "A streaming multipart parser for Python" +name = "python-multipart" +optional = false +python-versions = "*" +version = "0.0.5" + +[package.dependencies] +six = ">=1.4.0" + +[[package]] +category = "main" +description = "YAML parser and emitter for Python" +name = "pyyaml" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" +version = "5.3.1" + +[[package]] +category = "main" +description = "Python HTTP for Humans." +name = "requests" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" +version = "2.25.1" + +[package.dependencies] +certifi = ">=2017.4.17" +chardet = ">=3.0.2,<5" +idna = ">=2.5,<3" +urllib3 = ">=1.21.1,<1.27" + +[package.extras] +security = ["pyOpenSSL (>=0.14)", "cryptography (>=1.3.4)"] +socks = ["PySocks (>=1.5.6,<1.5.7 || >1.5.7)", "win-inet-pton"] + +[[package]] +category = "main" +description = "Validating URI References per RFC 3986" +name = "rfc3986" +optional = false +python-versions = "*" +version = "1.4.0" + +[package.dependencies] +[package.dependencies.idna] +optional = true +version = "*" + +[package.extras] +idna2008 = ["idna"] + +[[package]] +category = "main" +description = "Python 2 and 3 compatibility utilities" +name = "six" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" +version = "1.15.0" + +[[package]] +category = "main" +description = "Sniff out which async library your code is running under" +name = "sniffio" +optional = false +python-versions = ">=3.5" +version = "1.2.0" + +[[package]] +category = "main" +description = "A modern CSS selector implementation for Beautiful Soup." +marker = "python_version >= \"3.0\"" +name = "soupsieve" +optional = false +python-versions = ">=3.5" +version = "2.1" + +[[package]] +category = "main" +description = "CLI tool and Python utility functions for manipulating SQLite databases" +name = "sqlite-utils" +optional = false +python-versions = ">=3.6" +version = "2.23" + +[package.dependencies] +click = "*" +click-default-group = "*" +tabulate = "*" + +[package.extras] +docs = ["sphinx-rtd-theme", "sphinx-autobuild"] +test = ["pytest", "black", "hypothesis"] + +[[package]] +category = "main" +description = "Persistent dict in Python, backed up by sqlite3 and pickle, multithread-safe." +name = "sqlitedict" +optional = false +python-versions = "*" +version = "1.7.0" + +[[package]] +category = "main" +description = "Pretty-print tabular data" +name = "tabulate" +optional = false +python-versions = "*" +version = "0.8.7" + +[package.extras] +widechars = ["wcwidth"] + +[[package]] +category = "main" +description = "ANSII Color formatting for output in terminal." +name = "termcolor" +optional = false +python-versions = "*" +version = "1.1.0" + +[[package]] +category = "main" +description = "ASCII transliterations of Unicode text" +name = "unidecode" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +version = "1.1.2" + +[[package]] +category = "main" +description = "HTTP library with thread-safe connection pooling, file post, and more." +name = "urllib3" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, <4" +version = "1.26.2" + +[package.extras] +brotli = ["brotlipy (>=0.6.0)"] +secure = ["pyOpenSSL (>=0.14)", "cryptography (>=1.3.4)", "idna (>=2.0.0)", "certifi", "ipaddress"] +socks = ["PySocks (>=1.5.6,<1.5.7 || >1.5.7,<2.0)"] + +[[package]] +category = "main" +description = "The lightning-fast ASGI server." +name = "uvicorn" +optional = false +python-versions = "*" +version = "0.13.3" + +[package.dependencies] +click = ">=7.0.0,<8.0.0" +h11 = ">=0.8" + +[package.extras] +standard = ["websockets (>=8.0.0,<9.0.0)", "watchgod (>=0.6,<0.7)", "python-dotenv (>=0.13)", "PyYAML (>=5.1)", "httptools (>=0.1.0,<0.2.0)", "uvloop (>=0.14.0)", "colorama (>=0.4)"] + +[[package]] +category = "dev" +description = "Measures the displayed width of unicode strings in a terminal" +name = "wcwidth" +optional = false +python-versions = "*" +version = "0.2.5" + +[[package]] +category = "main" +description = "Very basic event publishing system" +name = "zope.event" +optional = false +python-versions = "*" +version = "4.5.0" + +[package.dependencies] +setuptools = "*" + +[package.extras] +docs = ["sphinx"] +test = ["zope.testrunner"] + +[[package]] +category = "main" +description = "Interfaces for Python" +name = "zope.interface" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" +version = "5.2.0" + +[package.dependencies] +setuptools = "*" + +[package.extras] +docs = ["sphinx", "repoze.sphinx.autointerface"] +test = ["coverage (>=5.0.3)", "zope.event", "zope.testing"] +testing = ["coverage (>=5.0.3)", "zope.event", "zope.testing"] + +[metadata] +content-hash = "229c0528177e952bf7594762f0eb78253796d67aa24a3135dd96b5f7d7a24630" +python-versions = "^3.8" + +[metadata.files] +aiofiles = [ + {file = "aiofiles-0.5.0-py3-none-any.whl", hash = "sha256:377fdf7815cc611870c59cbd07b68b180841d2a2b79812d8c218be02448c2acb"}, + {file = "aiofiles-0.5.0.tar.gz", hash = "sha256:98e6bcfd1b50f97db4980e182ddd509b7cc35909e903a8fe50d8849e02d815af"}, +] +asgi-csrf = [ + {file = "asgi-csrf-0.7.1.tar.gz", hash = "sha256:77bfb2a3311643fd2304d0f11f72160559acbbd1a35730a54208135ded3f21a3"}, + {file = "asgi_csrf-0.7.1-py3-none-any.whl", hash = "sha256:c6c862b4d9d78bb0297524c5e5447d26b210bef85c4304ace87d8ae1917f0efa"}, +] +asgiref = [ + {file = "asgiref-3.2.10-py3-none-any.whl", hash = "sha256:9fc6fb5d39b8af147ba40765234fa822b39818b12cc80b35ad9b0cef3a476aed"}, + {file = "asgiref-3.2.10.tar.gz", hash = "sha256:7e51911ee147dd685c3c8b805c0ad0cb58d360987b56953878f8c06d2d1c6f1a"}, +] +atomicwrites = [ + {file = "atomicwrites-1.4.0-py2.py3-none-any.whl", hash = "sha256:6d1784dea7c0c8d4a5172b6c620f40b6e4cbfdf96d783691f2e1302a7b88e197"}, + {file = "atomicwrites-1.4.0.tar.gz", hash = "sha256:ae70396ad1a434f9c7046fd2dd196fc04b12f9e91ffb859164193be8b6168a7a"}, +] +attrs = [ + {file = "attrs-20.3.0-py2.py3-none-any.whl", hash = "sha256:31b2eced602aa8423c2aea9c76a724617ed67cf9513173fd3a4f03e3a929c7e6"}, + {file = "attrs-20.3.0.tar.gz", hash = "sha256:832aa3cde19744e49938b91fea06d69ecb9e649c93ba974535d08ad92164f700"}, +] +beautifulsoup4 = [ + {file = "beautifulsoup4-4.9.3-py2-none-any.whl", hash = "sha256:4c98143716ef1cb40bf7f39a8e3eec8f8b009509e74904ba3a7b315431577e35"}, + {file = "beautifulsoup4-4.9.3-py3-none-any.whl", hash = "sha256:fff47e031e34ec82bf17e00da8f592fe7de69aeea38be00523c04623c04fb666"}, + {file = "beautifulsoup4-4.9.3.tar.gz", hash = "sha256:84729e322ad1d5b4d25f805bfa05b902dd96450f43842c4e99067d5e1369eb25"}, +] +bs4 = [ + {file = "bs4-0.0.1.tar.gz", hash = "sha256:36ecea1fd7cc5c0c6e4a1ff075df26d50da647b75376626cc186e2212886dd3a"}, +] +certifi = [ + {file = "certifi-2020.12.5-py2.py3-none-any.whl", hash = "sha256:719a74fb9e33b9bd44cc7f3a8d94bc35e4049deebe19ba7d8e108280cfd59830"}, + {file = "certifi-2020.12.5.tar.gz", hash = "sha256:1a4995114262bffbc2413b159f2a1a480c969de6e6eb13ee966d470af86af59c"}, +] +cffi = [ + {file = "cffi-1.14.4-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:ebb253464a5d0482b191274f1c8bf00e33f7e0b9c66405fbffc61ed2c839c775"}, + {file = "cffi-1.14.4-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:2c24d61263f511551f740d1a065eb0212db1dbbbbd241db758f5244281590c06"}, + {file = "cffi-1.14.4-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:9f7a31251289b2ab6d4012f6e83e58bc3b96bd151f5b5262467f4bb6b34a7c26"}, + {file = "cffi-1.14.4-cp27-cp27m-win32.whl", hash = "sha256:5cf4be6c304ad0b6602f5c4e90e2f59b47653ac1ed9c662ed379fe48a8f26b0c"}, + {file = "cffi-1.14.4-cp27-cp27m-win_amd64.whl", hash = "sha256:f60567825f791c6f8a592f3c6e3bd93dd2934e3f9dac189308426bd76b00ef3b"}, + {file = "cffi-1.14.4-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:c6332685306b6417a91b1ff9fae889b3ba65c2292d64bd9245c093b1b284809d"}, + {file = "cffi-1.14.4-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:d9efd8b7a3ef378dd61a1e77367f1924375befc2eba06168b6ebfa903a5e59ca"}, + {file = "cffi-1.14.4-cp35-cp35m-macosx_10_9_x86_64.whl", hash = "sha256:51a8b381b16ddd370178a65360ebe15fbc1c71cf6f584613a7ea08bfad946698"}, + {file = "cffi-1.14.4-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:1d2c4994f515e5b485fd6d3a73d05526aa0fcf248eb135996b088d25dfa1865b"}, + {file = "cffi-1.14.4-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:af5c59122a011049aad5dd87424b8e65a80e4a6477419c0c1015f73fb5ea0293"}, + {file = "cffi-1.14.4-cp35-cp35m-win32.whl", hash = "sha256:594234691ac0e9b770aee9fcdb8fa02c22e43e5c619456efd0d6c2bf276f3eb2"}, + {file = "cffi-1.14.4-cp35-cp35m-win_amd64.whl", hash = "sha256:64081b3f8f6f3c3de6191ec89d7dc6c86a8a43911f7ecb422c60e90c70be41c7"}, + {file = "cffi-1.14.4-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:f803eaa94c2fcda012c047e62bc7a51b0bdabda1cad7a92a522694ea2d76e49f"}, + {file = "cffi-1.14.4-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:105abaf8a6075dc96c1fe5ae7aae073f4696f2905fde6aeada4c9d2926752362"}, + {file = "cffi-1.14.4-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:0638c3ae1a0edfb77c6765d487fee624d2b1ee1bdfeffc1f0b58c64d149e7eec"}, + {file = "cffi-1.14.4-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:7c6b1dece89874d9541fc974917b631406233ea0440d0bdfbb8e03bf39a49b3b"}, + {file = "cffi-1.14.4-cp36-cp36m-win32.whl", hash = "sha256:155136b51fd733fa94e1c2ea5211dcd4c8879869008fc811648f16541bf99668"}, + {file = "cffi-1.14.4-cp36-cp36m-win_amd64.whl", hash = "sha256:6bc25fc545a6b3d57b5f8618e59fc13d3a3a68431e8ca5fd4c13241cd70d0009"}, + {file = "cffi-1.14.4-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:a7711edca4dcef1a75257b50a2fbfe92a65187c47dab5a0f1b9b332c5919a3fb"}, + {file = "cffi-1.14.4-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:00e28066507bfc3fe865a31f325c8391a1ac2916219340f87dfad602c3e48e5d"}, + {file = "cffi-1.14.4-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:798caa2a2384b1cbe8a2a139d80734c9db54f9cc155c99d7cc92441a23871c03"}, + {file = "cffi-1.14.4-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:a5ed8c05548b54b998b9498753fb9cadbfd92ee88e884641377d8a8b291bcc01"}, + {file = "cffi-1.14.4-cp37-cp37m-win32.whl", hash = "sha256:00a1ba5e2e95684448de9b89888ccd02c98d512064b4cb987d48f4b40aa0421e"}, + {file = "cffi-1.14.4-cp37-cp37m-win_amd64.whl", hash = "sha256:9cc46bc107224ff5b6d04369e7c595acb700c3613ad7bcf2e2012f62ece80c35"}, + {file = "cffi-1.14.4-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:df5169c4396adc04f9b0a05f13c074df878b6052430e03f50e68adf3a57aa28d"}, + {file = "cffi-1.14.4-cp38-cp38-manylinux1_i686.whl", hash = "sha256:9ffb888f19d54a4d4dfd4b3f29bc2c16aa4972f1c2ab9c4ab09b8ab8685b9c2b"}, + {file = "cffi-1.14.4-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:8d6603078baf4e11edc4168a514c5ce5b3ba6e3e9c374298cb88437957960a53"}, + {file = "cffi-1.14.4-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:d5ff0621c88ce83a28a10d2ce719b2ee85635e85c515f12bac99a95306da4b2e"}, + {file = "cffi-1.14.4-cp38-cp38-win32.whl", hash = "sha256:b4e248d1087abf9f4c10f3c398896c87ce82a9856494a7155823eb45a892395d"}, + {file = "cffi-1.14.4-cp38-cp38-win_amd64.whl", hash = "sha256:ec80dc47f54e6e9a78181ce05feb71a0353854cc26999db963695f950b5fb375"}, + {file = "cffi-1.14.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:840793c68105fe031f34d6a086eaea153a0cd5c491cde82a74b420edd0a2b909"}, + {file = "cffi-1.14.4-cp39-cp39-manylinux1_i686.whl", hash = "sha256:b18e0a9ef57d2b41f5c68beefa32317d286c3d6ac0484efd10d6e07491bb95dd"}, + {file = "cffi-1.14.4-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:045d792900a75e8b1e1b0ab6787dd733a8190ffcf80e8c8ceb2fb10a29ff238a"}, + {file = "cffi-1.14.4-cp39-cp39-win32.whl", hash = "sha256:ba4e9e0ae13fc41c6b23299545e5ef73055213e466bd107953e4a013a5ddd7e3"}, + {file = "cffi-1.14.4-cp39-cp39-win_amd64.whl", hash = "sha256:f032b34669220030f905152045dfa27741ce1a6db3324a5bc0b96b6c7420c87b"}, + {file = "cffi-1.14.4.tar.gz", hash = "sha256:1a465cbe98a7fd391d47dce4b8f7e5b921e6cd805ef421d04f5f66ba8f06086c"}, +] +chardet = [ + {file = "chardet-4.0.0-py2.py3-none-any.whl", hash = "sha256:f864054d66fd9118f2e67044ac8981a54775ec5b67aed0441892edb553d21da5"}, + {file = "chardet-4.0.0.tar.gz", hash = "sha256:0d6f53a15db4120f2b08c94f11e7d93d2c911ee118b6b30a04ec3ee8310179fa"}, +] +click = [ + {file = "click-7.1.2-py2.py3-none-any.whl", hash = "sha256:dacca89f4bfadd5de3d7489b7c8a566eee0d3676333fbb50030263894c38c0dc"}, + {file = "click-7.1.2.tar.gz", hash = "sha256:d2b5255c7c6349bc1bd1e59e08cd12acbbd63ce649f2588755783aa94dfb6b1a"}, +] +click-default-group = [ + {file = "click-default-group-1.2.2.tar.gz", hash = "sha256:d9560e8e8dfa44b3562fbc9425042a0fd6d21956fcc2db0077f63f34253ab904"}, +] +colorama = [ + {file = "colorama-0.4.4-py2.py3-none-any.whl", hash = "sha256:9f47eda37229f68eee03b24b9748937c7dc3868f906e8ba69fbcbdd3bc5dc3e2"}, + {file = "colorama-0.4.4.tar.gz", hash = "sha256:5941b2b48a20143d2267e95b1c2a7603ce057ee39fd88e7329b0c292aa16869b"}, +] +datasette = [ + {file = "datasette-0.50.2-py3-none-any.whl", hash = "sha256:0cc3e8c60bed79e401ad494669935529371d67ec56f1ab0357744d80552613e3"}, + {file = "datasette-0.50.2.tar.gz", hash = "sha256:72e3127a5007103e2b2e7e35172d7da256471c54370447199ffafb631526c0b4"}, +] +datasette-json-html = [ + {file = "datasette-json-html-0.6.1.tar.gz", hash = "sha256:adbd0f5d041a6748d08e055b422e7a3dd3490e266fdda61842edb2d4129defdd"}, + {file = "datasette_json_html-0.6.1-py3-none-any.whl", hash = "sha256:6909cee52e6fde80ae672861ba9e8c6abfee43bd9355b4f1dd60ecac173df472"}, +] +datasette-mask-columns = [ + {file = "datasette_mask_columns-0.2-py3-none-any.whl", hash = "sha256:327831de378566275b1270567c82dc20c473215cc535557dd552154a8346f03b"}, +] +datasette-pretty-json = [ + {file = "datasette-pretty-json-0.2.1.tar.gz", hash = "sha256:ca9cb2899ca7151c4adc0e9b5f4d9a0f33448b4943a70c4a607d5f07a440d32a"}, + {file = "datasette_pretty_json-0.2.1-py3-none-any.whl", hash = "sha256:fa1909744b9ffd76301534e4e0f8fcc962c86ee61df8e6a01a156bf9a4a64a20"}, +] +fire = [ + {file = "fire-0.3.1.tar.gz", hash = "sha256:9736a16227c3d469e5d2d296bce5b4d8fa8d7851e953bda327a455fc2994307f"}, +] +gevent = [ + {file = "gevent-20.12.1-cp27-cp27m-macosx_10_14_x86_64.whl", hash = "sha256:0f9fa230c5878704b9e286ad5038bac3b70d293bf10e9efa8b2ae1d7d80e7e08"}, + {file = "gevent-20.12.1-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:f020bfb34d57caa10029111be776524c378a4aac8417bc6fb1154b05e00fc220"}, + {file = "gevent-20.12.1-cp27-cp27m-win32.whl", hash = "sha256:e233ae153b586b61e492806d4cd1be2217de7441922c02053b67de14800bce96"}, + {file = "gevent-20.12.1-cp27-cp27m-win_amd64.whl", hash = "sha256:2d05f38a5ef1ebb7ceb692897674b11ba603914524765b989c65c020c7b08360"}, + {file = "gevent-20.12.1-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:ffa1be13963db6aa55c50d2fd4a656c82f53a03a47e37aaa69e79a488123538d"}, + {file = "gevent-20.12.1-cp35-cp35m-win32.whl", hash = "sha256:caec00914e8f21b2c77a29bbc2ef3abfeadf7515656e5451dfb14c2064733998"}, + {file = "gevent-20.12.1-cp35-cp35m-win_amd64.whl", hash = "sha256:19bd3fe60dec45fe6420b7772496950215f1b36701905876ba1644b6b2064163"}, + {file = "gevent-20.12.1-cp36-cp36m-win32.whl", hash = "sha256:9d001fc899db6e140110ae7484e58cd74b0dfa5cee021a0347f00bb441ac78bd"}, + {file = "gevent-20.12.1-cp36-cp36m-win_amd64.whl", hash = "sha256:b57586ad3fedf13d351d2559b70d6fe593c50400315d52bb3c072285da60fa37"}, + {file = "gevent-20.12.1-cp37-cp37m-macosx_10_14_x86_64.whl", hash = "sha256:69ddc1767a02f68e71d5e0d3215aa4d28872187715627f71ff0eadd7b7a5e7f4"}, + {file = "gevent-20.12.1-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:89c583744f91052ae987356660f5ed0b8fc59a1230b051d6ccc10d37a155fe01"}, + {file = "gevent-20.12.1-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:f3faf1834464f1b0731aa6346cd9f41029fa9e208d6ecbce4a736c19562c86aa"}, + {file = "gevent-20.12.1-cp37-cp37m-win32.whl", hash = "sha256:4baecba0fd614e14dc1f3f8c35616cb248cdb893de576150ed1fc7fc66b8ba3d"}, + {file = "gevent-20.12.1-cp37-cp37m-win_amd64.whl", hash = "sha256:4b0a5626c4e534d184cdf00d66f06de3885beafaaa5f7b98d47186ea175629a1"}, + {file = "gevent-20.12.1-cp38-cp38-macosx_10_14_x86_64.whl", hash = "sha256:81e38ed46e21e0b00b930efce1a1ff46c7722ad83d84052f71a757f23cbed1c0"}, + {file = "gevent-20.12.1-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:895c76a89907d9d37fdfaf5321cb0fff0cba396f003bedb4f5fc13836da6f250"}, + {file = "gevent-20.12.1-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:c3706a620e167c4bd007f16f113928324c4e07a7bae11d6d18d65f82abcd7a58"}, + {file = "gevent-20.12.1-cp38-cp38-win32.whl", hash = "sha256:ba244028225ff8d3a58f344fcd16ab05b0e3642b34d81f51f7fa3c70761f6c34"}, + {file = "gevent-20.12.1-cp38-cp38-win_amd64.whl", hash = "sha256:c570a2e3100f758a5c2f9b993ecf870ee784390e44e1a292c361d6b32fb3ad4c"}, + {file = "gevent-20.12.1-cp39-cp39-macosx_10_14_x86_64.whl", hash = "sha256:f857adbe1bf41e620d86173a53100f4ec328eba3089069a4815b3d9f4229dee8"}, + {file = "gevent-20.12.1-cp39-cp39-manylinux2010_x86_64.whl", hash = "sha256:bf946a99e364ebcc95b82c794d5d1a67f13115adbefab7b9e12791f13184cfd5"}, + {file = "gevent-20.12.1-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:60799fd7dcbb622f8435eb12436d48a8d27f8e7b3d23631e32ccc04ddd2097c2"}, + {file = "gevent-20.12.1-pp27-pypy_73-win32.whl", hash = "sha256:7a808c63f065a303bbbe87c5c0754e06abb1e23e18752f418dce1eb3189cb43d"}, + {file = "gevent-20.12.1.tar.gz", hash = "sha256:99b68765767bb3e2244a66b012883899a6f17c23b6dc1cd80b793df341e15f08"}, +] +greenlet = [ + {file = "greenlet-1.0.0-cp27-cp27m-macosx_10_14_x86_64.whl", hash = "sha256:1d1d4473ecb1c1d31ce8fd8d91e4da1b1f64d425c1dc965edc4ed2a63cfa67b2"}, + {file = "greenlet-1.0.0-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:cfd06e0f0cc8db2a854137bd79154b61ecd940dce96fad0cba23fe31de0b793c"}, + {file = "greenlet-1.0.0-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:eb333b90036358a0e2c57373f72e7648d7207b76ef0bd00a4f7daad1f79f5203"}, + {file = "greenlet-1.0.0-cp27-cp27m-win32.whl", hash = "sha256:1a1ada42a1fd2607d232ae11a7b3195735edaa49ea787a6d9e6a53afaf6f3476"}, + {file = "greenlet-1.0.0-cp27-cp27m-win_amd64.whl", hash = "sha256:f6f65bf54215e4ebf6b01e4bb94c49180a589573df643735107056f7a910275b"}, + {file = "greenlet-1.0.0-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:f59eded163d9752fd49978e0bab7a1ff21b1b8d25c05f0995d140cc08ac83379"}, + {file = "greenlet-1.0.0-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:875d4c60a6299f55df1c3bb870ebe6dcb7db28c165ab9ea6cdc5d5af36bb33ce"}, + {file = "greenlet-1.0.0-cp35-cp35m-macosx_10_14_x86_64.whl", hash = "sha256:1bb80c71de788b36cefb0c3bb6bfab306ba75073dbde2829c858dc3ad70f867c"}, + {file = "greenlet-1.0.0-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:b5f1b333015d53d4b381745f5de842f19fe59728b65f0fbb662dafbe2018c3a5"}, + {file = "greenlet-1.0.0-cp35-cp35m-manylinux2010_x86_64.whl", hash = "sha256:5352c15c1d91d22902582e891f27728d8dac3bd5e0ee565b6a9f575355e6d92f"}, + {file = "greenlet-1.0.0-cp35-cp35m-manylinux2014_aarch64.whl", hash = "sha256:2c65320774a8cd5fdb6e117c13afa91c4707548282464a18cf80243cf976b3e6"}, + {file = "greenlet-1.0.0-cp35-cp35m-manylinux2014_ppc64le.whl", hash = "sha256:111cfd92d78f2af0bc7317452bd93a477128af6327332ebf3c2be7df99566683"}, + {file = "greenlet-1.0.0-cp35-cp35m-win32.whl", hash = "sha256:cdb90267650c1edb54459cdb51dab865f6c6594c3a47ebd441bc493360c7af70"}, + {file = "greenlet-1.0.0-cp35-cp35m-win_amd64.whl", hash = "sha256:eac8803c9ad1817ce3d8d15d1bb82c2da3feda6bee1153eec5c58fa6e5d3f770"}, + {file = "greenlet-1.0.0-cp36-cp36m-macosx_10_14_x86_64.whl", hash = "sha256:c93d1a71c3fe222308939b2e516c07f35a849c5047f0197442a4d6fbcb4128ee"}, + {file = "greenlet-1.0.0-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:122c63ba795fdba4fc19c744df6277d9cfd913ed53d1a286f12189a0265316dd"}, + {file = "greenlet-1.0.0-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:c5b22b31c947ad8b6964d4ed66776bcae986f73669ba50620162ba7c832a6b6a"}, + {file = "greenlet-1.0.0-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:4365eccd68e72564c776418c53ce3c5af402bc526fe0653722bc89efd85bf12d"}, + {file = "greenlet-1.0.0-cp36-cp36m-manylinux2014_ppc64le.whl", hash = "sha256:da7d09ad0f24270b20f77d56934e196e982af0d0a2446120cb772be4e060e1a2"}, + {file = "greenlet-1.0.0-cp36-cp36m-win32.whl", hash = "sha256:647ba1df86d025f5a34043451d7c4a9f05f240bee06277a524daad11f997d1e7"}, + {file = "greenlet-1.0.0-cp36-cp36m-win_amd64.whl", hash = "sha256:e6e9fdaf6c90d02b95e6b0709aeb1aba5affbbb9ccaea5502f8638e4323206be"}, + {file = "greenlet-1.0.0-cp37-cp37m-macosx_10_14_x86_64.whl", hash = "sha256:62afad6e5fd70f34d773ffcbb7c22657e1d46d7fd7c95a43361de979f0a45aef"}, + {file = "greenlet-1.0.0-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:d3789c1c394944084b5e57c192889985a9f23bd985f6d15728c745d380318128"}, + {file = "greenlet-1.0.0-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:f5e2d36c86c7b03c94b8459c3bd2c9fe2c7dab4b258b8885617d44a22e453fb7"}, + {file = "greenlet-1.0.0-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:292e801fcb3a0b3a12d8c603c7cf340659ea27fd73c98683e75800d9fd8f704c"}, + {file = "greenlet-1.0.0-cp37-cp37m-manylinux2014_ppc64le.whl", hash = "sha256:f3dc68272990849132d6698f7dc6df2ab62a88b0d36e54702a8fd16c0490e44f"}, + {file = "greenlet-1.0.0-cp37-cp37m-win32.whl", hash = "sha256:7cd5a237f241f2764324396e06298b5dee0df580cf06ef4ada0ff9bff851286c"}, + {file = "greenlet-1.0.0-cp37-cp37m-win_amd64.whl", hash = "sha256:0ddd77586553e3daf439aa88b6642c5f252f7ef79a39271c25b1d4bf1b7cbb85"}, + {file = "greenlet-1.0.0-cp38-cp38-macosx_10_14_x86_64.whl", hash = "sha256:90b6a25841488cf2cb1c8623a53e6879573010a669455046df5f029d93db51b7"}, + {file = "greenlet-1.0.0-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:ed1d1351f05e795a527abc04a0d82e9aecd3bdf9f46662c36ff47b0b00ecaf06"}, + {file = "greenlet-1.0.0-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:94620ed996a7632723a424bccb84b07e7b861ab7bb06a5aeb041c111dd723d36"}, + {file = "greenlet-1.0.0-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:f97d83049715fd9dec7911860ecf0e17b48d8725de01e45de07d8ac0bd5bc378"}, + {file = "greenlet-1.0.0-cp38-cp38-manylinux2014_ppc64le.whl", hash = "sha256:0a77691f0080c9da8dfc81e23f4e3cffa5accf0f5b56478951016d7cfead9196"}, + {file = "greenlet-1.0.0-cp38-cp38-win32.whl", hash = "sha256:e1128e022d8dce375362e063754e129750323b67454cac5600008aad9f54139e"}, + {file = "greenlet-1.0.0-cp38-cp38-win_amd64.whl", hash = "sha256:5d4030b04061fdf4cbc446008e238e44936d77a04b2b32f804688ad64197953c"}, + {file = "greenlet-1.0.0-cp39-cp39-macosx_10_14_x86_64.whl", hash = "sha256:f8450d5ef759dbe59f84f2c9f77491bb3d3c44bc1a573746daf086e70b14c243"}, + {file = "greenlet-1.0.0-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:df8053867c831b2643b2c489fe1d62049a98566b1646b194cc815f13e27b90df"}, + {file = "greenlet-1.0.0-cp39-cp39-manylinux2010_x86_64.whl", hash = "sha256:df3e83323268594fa9755480a442cabfe8d82b21aba815a71acf1bb6c1776218"}, + {file = "greenlet-1.0.0-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:181300f826625b7fd1182205b830642926f52bd8cdb08b34574c9d5b2b1813f7"}, + {file = "greenlet-1.0.0-cp39-cp39-manylinux2014_ppc64le.whl", hash = "sha256:58ca0f078d1c135ecf1879d50711f925ee238fe773dfe44e206d7d126f5bc664"}, + {file = "greenlet-1.0.0-cp39-cp39-win32.whl", hash = "sha256:5f297cb343114b33a13755032ecf7109b07b9a0020e841d1c3cedff6602cc139"}, + {file = "greenlet-1.0.0-cp39-cp39-win_amd64.whl", hash = "sha256:5d69bbd9547d3bc49f8a545db7a0bd69f407badd2ff0f6e1a163680b5841d2b0"}, + {file = "greenlet-1.0.0.tar.gz", hash = "sha256:719e169c79255816cdcf6dccd9ed2d089a72a9f6c42273aae12d55e8d35bdcf8"}, +] +h11 = [ + {file = "h11-0.12.0-py3-none-any.whl", hash = "sha256:36a3cb8c0a032f56e2da7084577878a035d3b61d104230d4bd49c0c6b555a9c6"}, + {file = "h11-0.12.0.tar.gz", hash = "sha256:47222cb6067e4a307d535814917cd98fd0a57b6788ce715755fa2b6c28b56042"}, +] +httpcore = [ + {file = "httpcore-0.12.2-py3-none-any.whl", hash = "sha256:420700af11db658c782f7e8fda34f9dcd95e3ee93944dd97d78cb70247e0cd06"}, + {file = "httpcore-0.12.2.tar.gz", hash = "sha256:dd1d762d4f7c2702149d06be2597c35fb154c5eff9789a8c5823fbcf4d2978d6"}, +] +httpx = [ + {file = "httpx-0.16.1-py3-none-any.whl", hash = "sha256:9cffb8ba31fac6536f2c8cde30df859013f59e4bcc5b8d43901cb3654a8e0a5b"}, + {file = "httpx-0.16.1.tar.gz", hash = "sha256:126424c279c842738805974687e0518a94c7ae8d140cd65b9c4f77ac46ffa537"}, +] +humanize = [ + {file = "humanize-2.6.0-py3-none-any.whl", hash = "sha256:fd5b32945687443d5b8bc1e02fad027da1d293a9e963b3450122ad98ef534f21"}, + {file = "humanize-2.6.0.tar.gz", hash = "sha256:8ee358ea6c23de896b9d1925ebe6a8504bb2ba7e98d5ccf4d07ab7f3b28f3819"}, +] +hupper = [ + {file = "hupper-1.10.2-py2.py3-none-any.whl", hash = "sha256:5de835f3b58324af2a8a16f52270c4d1a3d1734c45eed94b77fd622aea737f29"}, + {file = "hupper-1.10.2.tar.gz", hash = "sha256:3818f53dabc24da66f65cf4878c1c7a9b5df0c46b813e014abdd7c569eb9a02a"}, +] +idna = [ + {file = "idna-2.10-py2.py3-none-any.whl", hash = "sha256:b97d804b1e9b523befed77c48dacec60e6dcb0b5391d57af6a65a312a90648c0"}, + {file = "idna-2.10.tar.gz", hash = "sha256:b307872f855b18632ce0c21c5e45be78c0ea7ae4c15c828c20788b26921eb3f6"}, +] +iso639 = [ + {file = "iso639-0.1.4.tar.gz", hash = "sha256:88b70cf6c64ee9c2c2972292818c8beb32db9ea6f4de1f8471a9b081a3d92e98"}, +] +itsdangerous = [ + {file = "itsdangerous-1.1.0-py2.py3-none-any.whl", hash = "sha256:b12271b2047cb23eeb98c8b5622e2e5c5e9abd9784a153e9d8ef9cb4dd09d749"}, + {file = "itsdangerous-1.1.0.tar.gz", hash = "sha256:321b033d07f2a4136d3ec762eac9f16a10ccd60f53c0c91af90217ace7ba1f19"}, +] +janus = [ + {file = "janus-0.5.0-py3-none-any.whl", hash = "sha256:f35c78f9cd13899438cce525e6ebabc7f8ad5f1b26b2f7d9ae153b4499acc84a"}, + {file = "janus-0.5.0.tar.gz", hash = "sha256:0700f5537d076521851d19b7625545c5e76f6d5792ab17984f28230adcc3b34c"}, +] +jinja2 = [ + {file = "Jinja2-2.11.2-py2.py3-none-any.whl", hash = "sha256:f0a4641d3cf955324a89c04f3d94663aa4d638abe8f733ecd3582848e1c37035"}, + {file = "Jinja2-2.11.2.tar.gz", hash = "sha256:89aab215427ef59c34ad58735269eb58b1a5808103067f7bb9d5836c651b3bb0"}, +] +langid = [ + {file = "langid-1.1.6.tar.gz", hash = "sha256:044bcae1912dab85c33d8e98f2811b8f4ff1213e5e9a9e9510137b84da2cb293"}, +] +markupsafe = [ + {file = "MarkupSafe-1.1.1-cp27-cp27m-macosx_10_6_intel.whl", hash = "sha256:09027a7803a62ca78792ad89403b1b7a73a01c8cb65909cd876f7fcebd79b161"}, + {file = "MarkupSafe-1.1.1-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:e249096428b3ae81b08327a63a485ad0878de3fb939049038579ac0ef61e17e7"}, + {file = "MarkupSafe-1.1.1-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:500d4957e52ddc3351cabf489e79c91c17f6e0899158447047588650b5e69183"}, + {file = "MarkupSafe-1.1.1-cp27-cp27m-win32.whl", hash = "sha256:b2051432115498d3562c084a49bba65d97cf251f5a331c64a12ee7e04dacc51b"}, + {file = "MarkupSafe-1.1.1-cp27-cp27m-win_amd64.whl", hash = "sha256:98c7086708b163d425c67c7a91bad6e466bb99d797aa64f965e9d25c12111a5e"}, + {file = "MarkupSafe-1.1.1-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:cd5df75523866410809ca100dc9681e301e3c27567cf498077e8551b6d20e42f"}, + {file = "MarkupSafe-1.1.1-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:43a55c2930bbc139570ac2452adf3d70cdbb3cfe5912c71cdce1c2c6bbd9c5d1"}, + {file = "MarkupSafe-1.1.1-cp34-cp34m-macosx_10_6_intel.whl", hash = "sha256:1027c282dad077d0bae18be6794e6b6b8c91d58ed8a8d89a89d59693b9131db5"}, + {file = "MarkupSafe-1.1.1-cp34-cp34m-manylinux1_i686.whl", hash = "sha256:62fe6c95e3ec8a7fad637b7f3d372c15ec1caa01ab47926cfdf7a75b40e0eac1"}, + {file = "MarkupSafe-1.1.1-cp34-cp34m-manylinux1_x86_64.whl", hash = "sha256:88e5fcfb52ee7b911e8bb6d6aa2fd21fbecc674eadd44118a9cc3863f938e735"}, + {file = "MarkupSafe-1.1.1-cp34-cp34m-win32.whl", hash = "sha256:ade5e387d2ad0d7ebf59146cc00c8044acbd863725f887353a10df825fc8ae21"}, + {file = "MarkupSafe-1.1.1-cp34-cp34m-win_amd64.whl", hash = "sha256:09c4b7f37d6c648cb13f9230d847adf22f8171b1ccc4d5682398e77f40309235"}, + {file = "MarkupSafe-1.1.1-cp35-cp35m-macosx_10_6_intel.whl", hash = "sha256:79855e1c5b8da654cf486b830bd42c06e8780cea587384cf6545b7d9ac013a0b"}, + {file = "MarkupSafe-1.1.1-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:c8716a48d94b06bb3b2524c2b77e055fb313aeb4ea620c8dd03a105574ba704f"}, + {file = "MarkupSafe-1.1.1-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:7c1699dfe0cf8ff607dbdcc1e9b9af1755371f92a68f706051cc8c37d447c905"}, + {file = "MarkupSafe-1.1.1-cp35-cp35m-win32.whl", hash = "sha256:6dd73240d2af64df90aa7c4e7481e23825ea70af4b4922f8ede5b9e35f78a3b1"}, + {file = "MarkupSafe-1.1.1-cp35-cp35m-win_amd64.whl", hash = "sha256:9add70b36c5666a2ed02b43b335fe19002ee5235efd4b8a89bfcf9005bebac0d"}, + {file = "MarkupSafe-1.1.1-cp36-cp36m-macosx_10_6_intel.whl", hash = "sha256:24982cc2533820871eba85ba648cd53d8623687ff11cbb805be4ff7b4c971aff"}, + {file = "MarkupSafe-1.1.1-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:00bc623926325b26bb9605ae9eae8a215691f33cae5df11ca5424f06f2d1f473"}, + {file = "MarkupSafe-1.1.1-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:717ba8fe3ae9cc0006d7c451f0bb265ee07739daf76355d06366154ee68d221e"}, + {file = "MarkupSafe-1.1.1-cp36-cp36m-win32.whl", hash = "sha256:535f6fc4d397c1563d08b88e485c3496cf5784e927af890fb3c3aac7f933ec66"}, + {file = "MarkupSafe-1.1.1-cp36-cp36m-win_amd64.whl", hash = "sha256:b1282f8c00509d99fef04d8ba936b156d419be841854fe901d8ae224c59f0be5"}, + {file = "MarkupSafe-1.1.1-cp37-cp37m-macosx_10_6_intel.whl", hash = "sha256:8defac2f2ccd6805ebf65f5eeb132adcf2ab57aa11fdf4c0dd5169a004710e7d"}, + {file = "MarkupSafe-1.1.1-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:46c99d2de99945ec5cb54f23c8cd5689f6d7177305ebff350a58ce5f8de1669e"}, + {file = "MarkupSafe-1.1.1-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:ba59edeaa2fc6114428f1637ffff42da1e311e29382d81b339c1817d37ec93c6"}, + {file = "MarkupSafe-1.1.1-cp37-cp37m-win32.whl", hash = "sha256:b00c1de48212e4cc9603895652c5c410df699856a2853135b3967591e4beebc2"}, + {file = "MarkupSafe-1.1.1-cp37-cp37m-win_amd64.whl", hash = "sha256:9bf40443012702a1d2070043cb6291650a0841ece432556f784f004937f0f32c"}, + {file = "MarkupSafe-1.1.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:6788b695d50a51edb699cb55e35487e430fa21f1ed838122d722e0ff0ac5ba15"}, + {file = "MarkupSafe-1.1.1-cp38-cp38-manylinux1_i686.whl", hash = "sha256:cdb132fc825c38e1aeec2c8aa9338310d29d337bebbd7baa06889d09a60a1fa2"}, + {file = "MarkupSafe-1.1.1-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:13d3144e1e340870b25e7b10b98d779608c02016d5184cfb9927a9f10c689f42"}, + {file = "MarkupSafe-1.1.1-cp38-cp38-win32.whl", hash = "sha256:596510de112c685489095da617b5bcbbac7dd6384aeebeda4df6025d0256a81b"}, + {file = "MarkupSafe-1.1.1-cp38-cp38-win_amd64.whl", hash = "sha256:e8313f01ba26fbbe36c7be1966a7b7424942f670f38e666995b88d012765b9be"}, + {file = "MarkupSafe-1.1.1.tar.gz", hash = "sha256:29872e92839765e546828bb7754a68c418d927cd064fd4708fab9fe9c8bb116b"}, +] +mergedeep = [ + {file = "mergedeep-1.3.1-py3-none-any.whl", hash = "sha256:4dd26fb3405d83e9b74f656bcc0463f70346aad3be7de481f6c156821bbe5f7a"}, + {file = "mergedeep-1.3.1.tar.gz", hash = "sha256:bfd361e0f841eec59af894d6c0a0eec741681e710bbffd8a4d2cb321ef6d5c5c"}, +] +more-itertools = [ + {file = "more-itertools-8.6.0.tar.gz", hash = "sha256:b3a9005928e5bed54076e6e549c792b306fddfe72b2d1d22dd63d42d5d3899cf"}, + {file = "more_itertools-8.6.0-py3-none-any.whl", hash = "sha256:8e1a2a43b2f2727425f2b5839587ae37093f19153dc26c0927d1048ff6557330"}, +] +numpy = [ + {file = "numpy-1.19.5-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:cc6bd4fd593cb261332568485e20a0712883cf631f6f5e8e86a52caa8b2b50ff"}, + {file = "numpy-1.19.5-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:aeb9ed923be74e659984e321f609b9ba54a48354bfd168d21a2b072ed1e833ea"}, + {file = "numpy-1.19.5-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:8b5e972b43c8fc27d56550b4120fe6257fdc15f9301914380b27f74856299fea"}, + {file = "numpy-1.19.5-cp36-cp36m-manylinux2010_i686.whl", hash = "sha256:43d4c81d5ffdff6bae58d66a3cd7f54a7acd9a0e7b18d97abb255defc09e3140"}, + {file = "numpy-1.19.5-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:a4646724fba402aa7504cd48b4b50e783296b5e10a524c7a6da62e4a8ac9698d"}, + {file = "numpy-1.19.5-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:2e55195bc1c6b705bfd8ad6f288b38b11b1af32f3c8289d6c50d47f950c12e76"}, + {file = "numpy-1.19.5-cp36-cp36m-win32.whl", hash = "sha256:39b70c19ec771805081578cc936bbe95336798b7edf4732ed102e7a43ec5c07a"}, + {file = "numpy-1.19.5-cp36-cp36m-win_amd64.whl", hash = "sha256:dbd18bcf4889b720ba13a27ec2f2aac1981bd41203b3a3b27ba7a33f88ae4827"}, + {file = "numpy-1.19.5-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:603aa0706be710eea8884af807b1b3bc9fb2e49b9f4da439e76000f3b3c6ff0f"}, + {file = "numpy-1.19.5-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:cae865b1cae1ec2663d8ea56ef6ff185bad091a5e33ebbadd98de2cfa3fa668f"}, + {file = "numpy-1.19.5-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:36674959eed6957e61f11c912f71e78857a8d0604171dfd9ce9ad5cbf41c511c"}, + {file = "numpy-1.19.5-cp37-cp37m-manylinux2010_i686.whl", hash = "sha256:06fab248a088e439402141ea04f0fffb203723148f6ee791e9c75b3e9e82f080"}, + {file = "numpy-1.19.5-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:6149a185cece5ee78d1d196938b2a8f9d09f5a5ebfbba66969302a778d5ddd1d"}, + {file = "numpy-1.19.5-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:50a4a0ad0111cc1b71fa32dedd05fa239f7fb5a43a40663269bb5dc7877cfd28"}, + {file = "numpy-1.19.5-cp37-cp37m-win32.whl", hash = "sha256:d051ec1c64b85ecc69531e1137bb9751c6830772ee5c1c426dbcfe98ef5788d7"}, + {file = "numpy-1.19.5-cp37-cp37m-win_amd64.whl", hash = "sha256:a12ff4c8ddfee61f90a1633a4c4afd3f7bcb32b11c52026c92a12e1325922d0d"}, + {file = "numpy-1.19.5-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:cf2402002d3d9f91c8b01e66fbb436a4ed01c6498fffed0e4c7566da1d40ee1e"}, + {file = "numpy-1.19.5-cp38-cp38-manylinux1_i686.whl", hash = "sha256:1ded4fce9cfaaf24e7a0ab51b7a87be9038ea1ace7f34b841fe3b6894c721d1c"}, + {file = "numpy-1.19.5-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:012426a41bc9ab63bb158635aecccc7610e3eff5d31d1eb43bc099debc979d94"}, + {file = "numpy-1.19.5-cp38-cp38-manylinux2010_i686.whl", hash = "sha256:759e4095edc3c1b3ac031f34d9459fa781777a93ccc633a472a5468587a190ff"}, + {file = "numpy-1.19.5-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:a9d17f2be3b427fbb2bce61e596cf555d6f8a56c222bd2ca148baeeb5e5c783c"}, + {file = "numpy-1.19.5-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:99abf4f353c3d1a0c7a5f27699482c987cf663b1eac20db59b8c7b061eabd7fc"}, + {file = "numpy-1.19.5-cp38-cp38-win32.whl", hash = "sha256:384ec0463d1c2671170901994aeb6dce126de0a95ccc3976c43b0038a37329c2"}, + {file = "numpy-1.19.5-cp38-cp38-win_amd64.whl", hash = "sha256:811daee36a58dc79cf3d8bdd4a490e4277d0e4b7d103a001a4e73ddb48e7e6aa"}, + {file = "numpy-1.19.5-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:c843b3f50d1ab7361ca4f0b3639bf691569493a56808a0b0c54a051d260b7dbd"}, + {file = "numpy-1.19.5-cp39-cp39-manylinux1_i686.whl", hash = "sha256:d6631f2e867676b13026e2846180e2c13c1e11289d67da08d71cacb2cd93d4aa"}, + {file = "numpy-1.19.5-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:7fb43004bce0ca31d8f13a6eb5e943fa73371381e53f7074ed21a4cb786c32f8"}, + {file = "numpy-1.19.5-cp39-cp39-manylinux2010_i686.whl", hash = "sha256:2ea52bd92ab9f768cc64a4c3ef8f4b2580a17af0a5436f6126b08efbd1838371"}, + {file = "numpy-1.19.5-cp39-cp39-manylinux2010_x86_64.whl", hash = "sha256:400580cbd3cff6ffa6293df2278c75aef2d58d8d93d3c5614cd67981dae68ceb"}, + {file = "numpy-1.19.5-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:df609c82f18c5b9f6cb97271f03315ff0dbe481a2a02e56aeb1b1a985ce38e60"}, + {file = "numpy-1.19.5-cp39-cp39-win32.whl", hash = "sha256:ab83f24d5c52d60dbc8cd0528759532736b56db58adaa7b5f1f76ad551416a1e"}, + {file = "numpy-1.19.5-cp39-cp39-win_amd64.whl", hash = "sha256:0eef32ca3132a48e43f6a0f5a82cb508f22ce5a3d6f67a8329c81c8e226d3f6e"}, + {file = "numpy-1.19.5-pp36-pypy36_pp73-manylinux2010_x86_64.whl", hash = "sha256:a0d53e51a6cb6f0d9082decb7a4cb6dfb33055308c4c44f53103c073f649af73"}, + {file = "numpy-1.19.5.zip", hash = "sha256:a76f502430dd98d7546e1ea2250a7360c065a5fdea52b2dffe8ae7180909b6f4"}, +] +packaging = [ + {file = "packaging-20.8-py2.py3-none-any.whl", hash = "sha256:24e0da08660a87484d1602c30bb4902d74816b6985b93de36926f5bc95741858"}, + {file = "packaging-20.8.tar.gz", hash = "sha256:78598185a7008a470d64526a8059de9aaa449238f280fc9eb6b13ba6c4109093"}, +] +pint = [ + {file = "Pint-0.16.1-py2.py3-none-any.whl", hash = "sha256:63ccb7153754923fd95477be69dcf8d7d0764ec2ebb3f6945f920c31fdf13392"}, + {file = "Pint-0.16.1.tar.gz", hash = "sha256:d43a2e9ae003164978b60fdf8cd920d8581e1a5991df8dded29b00f4850ec83a"}, +] +pluggy = [ + {file = "pluggy-0.13.1-py2.py3-none-any.whl", hash = "sha256:966c145cd83c96502c3c3868f50408687b38434af77734af1e9ca461a4081d2d"}, + {file = "pluggy-0.13.1.tar.gz", hash = "sha256:15b2acde666561e1298d71b523007ed7364de07029219b604cf808bfa1c765b0"}, +] +py = [ + {file = "py-1.10.0-py2.py3-none-any.whl", hash = "sha256:3b80836aa6d1feeaa108e046da6423ab8f6ceda6468545ae8d02d9d58d18818a"}, + {file = "py-1.10.0.tar.gz", hash = "sha256:21b81bda15b66ef5e1a777a21c4dcd9c20ad3efd0b3f817e7a809035269e1bd3"}, +] +pycparser = [ + {file = "pycparser-2.20-py2.py3-none-any.whl", hash = "sha256:7582ad22678f0fcd81102833f60ef8d0e57288b6b5fb00323d101be910e35705"}, + {file = "pycparser-2.20.tar.gz", hash = "sha256:2d475327684562c3a96cc71adf7dc8c4f0565175cf86b6d7a404ff4c771f15f0"}, +] +pyparsing = [ + {file = "pyparsing-2.4.7-py2.py3-none-any.whl", hash = "sha256:ef9d7589ef3c200abe66653d3f1ab1033c3c419ae9b9bdb1240a85b024efc88b"}, + {file = "pyparsing-2.4.7.tar.gz", hash = "sha256:c203ec8783bf771a155b207279b9bccb8dea02d8f0c9e5f8ead507bc3246ecc1"}, +] +pytest = [ + {file = "pytest-5.4.3-py3-none-any.whl", hash = "sha256:5c0db86b698e8f170ba4582a492248919255fcd4c79b1ee64ace34301fb589a1"}, + {file = "pytest-5.4.3.tar.gz", hash = "sha256:7979331bfcba207414f5e1263b5a0f8f521d0f457318836a7355531ed1a4c7d8"}, +] +python-baseconv = [ + {file = "python-baseconv-1.2.2.tar.gz", hash = "sha256:0539f8bd0464013b05ad62e0a1673f0ac9086c76b43ebf9f833053527cd9931b"}, +] +python-multipart = [ + {file = "python-multipart-0.0.5.tar.gz", hash = "sha256:f7bb5f611fc600d15fa47b3974c8aa16e93724513b49b5f95c81e6624c83fa43"}, +] +pyyaml = [ + {file = "PyYAML-5.3.1-cp27-cp27m-win32.whl", hash = "sha256:74809a57b329d6cc0fdccee6318f44b9b8649961fa73144a98735b0aaf029f1f"}, + {file = "PyYAML-5.3.1-cp27-cp27m-win_amd64.whl", hash = "sha256:240097ff019d7c70a4922b6869d8a86407758333f02203e0fc6ff79c5dcede76"}, + {file = "PyYAML-5.3.1-cp35-cp35m-win32.whl", hash = "sha256:4f4b913ca1a7319b33cfb1369e91e50354d6f07a135f3b901aca02aa95940bd2"}, + {file = "PyYAML-5.3.1-cp35-cp35m-win_amd64.whl", hash = "sha256:cc8955cfbfc7a115fa81d85284ee61147059a753344bc51098f3ccd69b0d7e0c"}, + {file = "PyYAML-5.3.1-cp36-cp36m-win32.whl", hash = "sha256:7739fc0fa8205b3ee8808aea45e968bc90082c10aef6ea95e855e10abf4a37b2"}, + {file = "PyYAML-5.3.1-cp36-cp36m-win_amd64.whl", hash = "sha256:69f00dca373f240f842b2931fb2c7e14ddbacd1397d57157a9b005a6a9942648"}, + {file = "PyYAML-5.3.1-cp37-cp37m-win32.whl", hash = "sha256:d13155f591e6fcc1ec3b30685d50bf0711574e2c0dfffd7644babf8b5102ca1a"}, + {file = "PyYAML-5.3.1-cp37-cp37m-win_amd64.whl", hash = "sha256:73f099454b799e05e5ab51423c7bcf361c58d3206fa7b0d555426b1f4d9a3eaf"}, + {file = "PyYAML-5.3.1-cp38-cp38-win32.whl", hash = "sha256:06a0d7ba600ce0b2d2fe2e78453a470b5a6e000a985dd4a4e54e436cc36b0e97"}, + {file = "PyYAML-5.3.1-cp38-cp38-win_amd64.whl", hash = "sha256:95f71d2af0ff4227885f7a6605c37fd53d3a106fcab511b8860ecca9fcf400ee"}, + {file = "PyYAML-5.3.1.tar.gz", hash = "sha256:b8eac752c5e14d3eca0e6dd9199cd627518cb5ec06add0de9d32baeee6fe645d"}, +] +requests = [ + {file = "requests-2.25.1-py2.py3-none-any.whl", hash = "sha256:c210084e36a42ae6b9219e00e48287def368a26d03a048ddad7bfee44f75871e"}, + {file = "requests-2.25.1.tar.gz", hash = "sha256:27973dd4a904a4f13b263a19c866c13b92a39ed1c964655f025f3f8d3d75b804"}, +] +rfc3986 = [ + {file = "rfc3986-1.4.0-py2.py3-none-any.whl", hash = "sha256:af9147e9aceda37c91a05f4deb128d4b4b49d6b199775fd2d2927768abdc8f50"}, + {file = "rfc3986-1.4.0.tar.gz", hash = "sha256:112398da31a3344dc25dbf477d8df6cb34f9278a94fee2625d89e4514be8bb9d"}, +] +six = [ + {file = "six-1.15.0-py2.py3-none-any.whl", hash = "sha256:8b74bedcbbbaca38ff6d7491d76f2b06b3592611af620f8426e82dddb04a5ced"}, + {file = "six-1.15.0.tar.gz", hash = "sha256:30639c035cdb23534cd4aa2dd52c3bf48f06e5f4a941509c8bafd8ce11080259"}, +] +sniffio = [ + {file = "sniffio-1.2.0-py3-none-any.whl", hash = "sha256:471b71698eac1c2112a40ce2752bb2f4a4814c22a54a3eed3676bc0f5ca9f663"}, + {file = "sniffio-1.2.0.tar.gz", hash = "sha256:c4666eecec1d3f50960c6bdf61ab7bc350648da6c126e3cf6898d8cd4ddcd3de"}, +] +soupsieve = [ + {file = "soupsieve-2.1-py3-none-any.whl", hash = "sha256:4bb21a6ee4707bf43b61230e80740e71bfe56e55d1f1f50924b087bb2975c851"}, + {file = "soupsieve-2.1.tar.gz", hash = "sha256:6dc52924dc0bc710a5d16794e6b3480b2c7c08b07729505feab2b2c16661ff6e"}, +] +sqlite-utils = [ + {file = "sqlite-utils-2.23.tar.gz", hash = "sha256:1b67e19bbb52013796c98ed9f91f5002555c6be66ebb092dfd6a8924ec82ff48"}, + {file = "sqlite_utils-2.23-py3-none-any.whl", hash = "sha256:bb33cfe8d97f5d350b259985220de5fadc412186bfee6c8eb1e233d86dd50d8c"}, +] +sqlitedict = [ + {file = "sqlitedict-1.7.0.tar.gz", hash = "sha256:2affcc301aacd4da7511692601ecbde392294205af418498f7d6d3ec0dbcad56"}, +] +tabulate = [ + {file = "tabulate-0.8.7-py3-none-any.whl", hash = "sha256:ac64cb76d53b1231d364babcd72abbb16855adac7de6665122f97b593f1eb2ba"}, + {file = "tabulate-0.8.7.tar.gz", hash = "sha256:db2723a20d04bcda8522165c73eea7c300eda74e0ce852d9022e0159d7895007"}, +] +termcolor = [ + {file = "termcolor-1.1.0.tar.gz", hash = "sha256:1d6d69ce66211143803fbc56652b41d73b4a400a2891d7bf7a1cdf4c02de613b"}, +] +unidecode = [ + {file = "Unidecode-1.1.2-py2.py3-none-any.whl", hash = "sha256:4c9d15d2f73eb0d2649a151c566901f80a030da1ccb0a2043352e1dbf647586b"}, + {file = "Unidecode-1.1.2.tar.gz", hash = "sha256:a039f89014245e0cad8858976293e23501accc9ff5a7bdbc739a14a2b7b85cdc"}, +] +urllib3 = [ + {file = "urllib3-1.26.2-py2.py3-none-any.whl", hash = "sha256:d8ff90d979214d7b4f8ce956e80f4028fc6860e4431f731ea4a8c08f23f99473"}, + {file = "urllib3-1.26.2.tar.gz", hash = "sha256:19188f96923873c92ccb987120ec4acaa12f0461fa9ce5d3d0772bc965a39e08"}, +] +uvicorn = [ + {file = "uvicorn-0.13.3-py3-none-any.whl", hash = "sha256:1079c50a06f6338095b4f203e7861dbff318dde5f22f3a324fc6e94c7654164c"}, + {file = "uvicorn-0.13.3.tar.gz", hash = "sha256:ef1e0bb5f7941c6fe324e06443ddac0331e1632a776175f87891c7bd02694355"}, +] +wcwidth = [ + {file = "wcwidth-0.2.5-py2.py3-none-any.whl", hash = "sha256:beb4802a9cebb9144e99086eff703a642a13d6a0052920003a230f3294bbe784"}, + {file = "wcwidth-0.2.5.tar.gz", hash = "sha256:c4d647b99872929fdb7bdcaa4fbe7f01413ed3d98077df798530e5b04f116c83"}, +] +"zope.event" = [ + {file = "zope.event-4.5.0-py2.py3-none-any.whl", hash = "sha256:2666401939cdaa5f4e0c08cf7f20c9b21423b95e88f4675b1443973bdb080c42"}, + {file = "zope.event-4.5.0.tar.gz", hash = "sha256:5e76517f5b9b119acf37ca8819781db6c16ea433f7e2062c4afc2b6fbedb1330"}, +] +"zope.interface" = [ + {file = "zope.interface-5.2.0-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:518950fe6a5d56f94ba125107895f938a4f34f704c658986eae8255edb41163b"}, + {file = "zope.interface-5.2.0-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:6278c080d4afffc9016e14325f8734456831124e8c12caa754fd544435c08386"}, + {file = "zope.interface-5.2.0-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:538298e4e113ccb8b41658d5a4b605bebe75e46a30ceca22a5a289cf02c80bec"}, + {file = "zope.interface-5.2.0-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:778d0ec38bbd288b150a3ae363c8ffd88d2207a756842495e9bffd8a8afbc89a"}, + {file = "zope.interface-5.2.0-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:64ea6c221aeee4796860405e1aedec63424cda4202a7ad27a5066876db5b0fd2"}, + {file = "zope.interface-5.2.0-cp27-cp27m-win32.whl", hash = "sha256:92dc0fb79675882d0b6138be4bf0cec7ea7c7eede60aaca78303d8e8dbdaa523"}, + {file = "zope.interface-5.2.0-cp27-cp27m-win_amd64.whl", hash = "sha256:844fad925ac5c2ad4faaceb3b2520ad016b5280105c6e16e79838cf951903a7b"}, + {file = "zope.interface-5.2.0-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:588384d70a0f19b47409cfdb10e0c27c20e4293b74fc891df3d8eb47782b8b3e"}, + {file = "zope.interface-5.2.0-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:07d61722dd7d85547b7c6b0f5486b4338001fab349f2ac5cabc0b7182eb3425d"}, + {file = "zope.interface-5.2.0-cp27-cp27mu-manylinux2010_i686.whl", hash = "sha256:495b63fd0302f282ee6c1e6ea0f1c12cb3d1a49c8292d27287f01845ff252a96"}, + {file = "zope.interface-5.2.0-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:299bde0ab9e5c4a92f01a152b7fbabb460f31343f1416f9b7b983167ab1e33bc"}, + {file = "zope.interface-5.2.0-cp35-cp35m-macosx_10_6_intel.whl", hash = "sha256:32546af61a9a9b141ca38d971aa6eb9800450fa6620ce6323cc30eec447861f3"}, + {file = "zope.interface-5.2.0-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:2ab88d8f228f803fcb8cb7d222c579d13dab2d3622c51e8cf321280da01102a7"}, + {file = "zope.interface-5.2.0-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:cbd0f2cbd8689861209cd89141371d3a22a11613304d1f0736492590aa0ab332"}, + {file = "zope.interface-5.2.0-cp35-cp35m-manylinux2010_i686.whl", hash = "sha256:21e49123f375703cf824214939d39df0af62c47d122d955b2a8d9153ea08cfd5"}, + {file = "zope.interface-5.2.0-cp35-cp35m-manylinux2010_x86_64.whl", hash = "sha256:eccac3d9aadc68e994b6d228cb0c8919fc47a5350d85a1b4d3d81d1e98baf40c"}, + {file = "zope.interface-5.2.0-cp35-cp35m-manylinux2014_aarch64.whl", hash = "sha256:42b278ac0989d6f5cf58d7e0828ea6b5951464e3cf2ff229dd09a96cb6ba0c86"}, + {file = "zope.interface-5.2.0-cp35-cp35m-win32.whl", hash = "sha256:83b4aa5344cce005a9cff5d0321b2e318e871cc1dfc793b66c32dd4f59e9770d"}, + {file = "zope.interface-5.2.0-cp35-cp35m-win_amd64.whl", hash = "sha256:4df9afd17bd5477e9f8c8b6bb8507e18dd0f8b4efe73bb99729ff203279e9e3b"}, + {file = "zope.interface-5.2.0-cp36-cp36m-macosx_10_6_intel.whl", hash = "sha256:1743bcfe45af8846b775086471c28258f4c6e9ee8ef37484de4495f15a98b549"}, + {file = "zope.interface-5.2.0-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:aedc6c672b351afe6dfe17ff83ee5e7eb6ed44718f879a9328a68bdb20b57e11"}, + {file = "zope.interface-5.2.0-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:4af87cdc0d4b14e600e6d3d09793dce3b7171348a094ba818e2a68ae7ee67546"}, + {file = "zope.interface-5.2.0-cp36-cp36m-manylinux2010_i686.whl", hash = "sha256:b7a00ecb1434f8183395fac5366a21ee73d14900082ca37cf74993cf46baa56c"}, + {file = "zope.interface-5.2.0-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:8ceb3667dd13b8133f2e4d637b5b00f240f066448e2aa89a41f4c2d78a26ce50"}, + {file = "zope.interface-5.2.0-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:efef581c8ba4d990770875e1a2218e856849d32ada2680e53aebc5d154a17e20"}, + {file = "zope.interface-5.2.0-cp36-cp36m-win32.whl", hash = "sha256:e4bc372b953bf6cec65a8d48482ba574f6e051621d157cf224227dbb55486b1e"}, + {file = "zope.interface-5.2.0-cp36-cp36m-win_amd64.whl", hash = "sha256:3cc94c69f6bd48ed86e8e24f358cb75095c8129827df1298518ab860115269a4"}, + {file = "zope.interface-5.2.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:ba32f4a91c1cb7314c429b03afbf87b1fff4fb1c8db32260e7310104bd77f0c7"}, + {file = "zope.interface-5.2.0-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:1b5f6c8fff4ed32aa2dd43e84061bc8346f32d3ba6ad6e58f088fe109608f102"}, + {file = "zope.interface-5.2.0-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:efd550b3da28195746bb43bd1d815058181a7ca6d9d6aa89dd37f5eefe2cacb7"}, + {file = "zope.interface-5.2.0-cp37-cp37m-manylinux2010_i686.whl", hash = "sha256:aab9f1e34d810feb00bf841993552b8fcc6ae71d473c505381627143d0018a6a"}, + {file = "zope.interface-5.2.0-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:55465121e72e208a7b69b53de791402affe6165083b2ea71b892728bd19ba9ae"}, + {file = "zope.interface-5.2.0-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:32b40a4c46d199827d79c86bb8cb88b1bbb764f127876f2cb6f3a47f63dbada3"}, + {file = "zope.interface-5.2.0-cp37-cp37m-win32.whl", hash = "sha256:abb61afd84f23099ac6099d804cdba9bd3b902aaaded3ffff47e490b0a495520"}, + {file = "zope.interface-5.2.0-cp37-cp37m-win_amd64.whl", hash = "sha256:21f579134a47083ffb5ddd1307f0405c91aa8b61ad4be6fd5af0171474fe0c45"}, + {file = "zope.interface-5.2.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:4c48ddb63e2b20fba4c6a2bf81b4d49e99b6d4587fb67a6cd33a2c1f003af3e3"}, + {file = "zope.interface-5.2.0-cp38-cp38-manylinux1_i686.whl", hash = "sha256:2dcab01c660983ba5e5a612e0c935141ccbee67d2e2e14b833e01c2354bd8034"}, + {file = "zope.interface-5.2.0-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:150e8bcb7253a34a4535aeea3de36c0bb3b1a6a47a183a95d65a194b3e07f232"}, + {file = "zope.interface-5.2.0-cp38-cp38-manylinux2010_i686.whl", hash = "sha256:681dbb33e2b40262b33fd383bae63c36d33fd79fa1a8e4092945430744ffd34a"}, + {file = "zope.interface-5.2.0-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:2ced4c35061eea623bc84c7711eedce8ecc3c2c51cd9c6afa6290df3bae9e104"}, + {file = "zope.interface-5.2.0-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:f37d45fab14ffef9d33a0dc3bc59ce0c5313e2253323312d47739192da94f5fd"}, + {file = "zope.interface-5.2.0-cp38-cp38-win32.whl", hash = "sha256:9789bd945e9f5bd026ed3f5b453d640befb8b1fc33a779c1fe8d3eb21fe3fb4a"}, + {file = "zope.interface-5.2.0-cp38-cp38-win_amd64.whl", hash = "sha256:0a990dcc97806e5980bbb54b2e46b9cde9e48932d8e6984daf71ef1745516123"}, + {file = "zope.interface-5.2.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:4b94df9f2fdde7b9314321bab8448e6ad5a23b80542dcab53e329527d4099dcb"}, + {file = "zope.interface-5.2.0-cp39-cp39-manylinux1_i686.whl", hash = "sha256:6936aa9da390402d646a32a6a38d5409c2d2afb2950f045a7d02ab25a4e7d08d"}, + {file = "zope.interface-5.2.0-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:adf9ee115ae8ff8b6da4b854b4152f253b390ba64407a22d75456fe07dcbda65"}, + {file = "zope.interface-5.2.0-cp39-cp39-manylinux2010_i686.whl", hash = "sha256:f44906f70205d456d503105023041f1e63aece7623b31c390a0103db4de17537"}, + {file = "zope.interface-5.2.0-cp39-cp39-manylinux2010_x86_64.whl", hash = "sha256:f057897711a630a0b7a6a03f1acf379b6ba25d37dc5dc217a97191984ba7f2fc"}, + {file = "zope.interface-5.2.0-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:05a97ba92c1c7c26f25c9f671aa1ef85ffead6cdad13770e5b689cf983adc7e1"}, + {file = "zope.interface-5.2.0-cp39-cp39-win32.whl", hash = "sha256:27c267dc38a0f0079e96a2945ee65786d38ef111e413c702fbaaacbab6361d00"}, + {file = "zope.interface-5.2.0-cp39-cp39-win_amd64.whl", hash = "sha256:a2b6d6eb693bc2fc6c484f2e5d93bd0b0da803fa77bf974f160533e555e4d095"}, + {file = "zope.interface-5.2.0.tar.gz", hash = "sha256:8251f06a77985a2729a8bdbefbae79ee78567dddc3acbd499b87e705ca59fe24"}, +] diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..e558bc3 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,29 @@ +[tool.poetry] +name = "calishot" +version = "0.1.0" +description = "" +authors = ["Your Name "] + +[tool.poetry.dependencies] +python = "^3.8" +sqlite-utils = "^2.8" +bs4 = "^0.0.1" +gevent = "^20.5.0" +datasette-pretty-json = "^0.2" +datasette-json-html = "^0.6" +datasette-mask-columns = "^0.2" +requests = "^2.24.0" +humanize = "^2.5.0" +langid = "^1.1.6" +iso639 = "^0.1.4" +unidecode = "^1.1.1" +datasette = "^0.50.2" +sqlitedict = "^1.7.0" +fire = "^0.3.1" + +[tool.poetry.dev-dependencies] +pytest = "^5.2" + +[build-system] +requires = ["poetry>=0.12"] +build-backend = "poetry.masonry.api"