From 57f74ef1aa829d3c764d4426fd6d89d6c45e94d2 Mon Sep 17 00:00:00 2001 From: Geoffrey Aldebert Date: Fri, 10 Jun 2022 20:53:38 +0200 Subject: [PATCH 01/14] Update with new vars --- config.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/config.py b/config.py index 990fb26..40435eb 100644 --- a/config.py +++ b/config.py @@ -15,3 +15,6 @@ # e.g. REFERRERS_FILTER = ['data.gouv.fr'] will match 'demo.data.gouv.fr' REFERRERS_FILTER = None PANDAS_PROFILING_CONFIG_MIN = False +MINIO_URL= 'http://localhost:9000' +MINIO_USER = 'geoffrey' +MINIO_PASSWORD = 'geoffrey' \ No newline at end of file From 6194adaba9f140b26952f093a8600936e2368174 Mon Sep 17 00:00:00 2001 From: Geoffrey Aldebert Date: Fri, 10 Jun 2022 20:53:53 +0200 Subject: [PATCH 02/14] Update requirements --- requirements/develop.pip | 2 +- requirements/install.pip | 7 +++++-- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/requirements/develop.pip b/requirements/develop.pip index 042c859..4a0b60a 100644 --- a/requirements/develop.pip +++ b/requirements/develop.pip @@ -2,4 +2,4 @@ flake8==3.5.0 invoke==1.1.1 pytest-cov==2.5.1 -wheel +wheel \ No newline at end of file diff --git a/requirements/install.pip b/requirements/install.pip index 4245571..97c394b 100644 --- a/requirements/install.pip +++ b/requirements/install.pip @@ -1,5 +1,5 @@ click_default_group==1.2.2 -click==7.1.2 +click==8.1.3 agate==1.6.1 agate-sql==0.5.5 aiohttp==3.7.3 @@ -11,5 +11,8 @@ sentry-sdk==1.0.0 cchardet==2.1.7 python-stdnum==1.15 aiosqlite==0.16.1 -pandas==1.2.4 +pandas==1.4.2 pandas-profiling==2.11.0 +requests==2.27.1 +udata-event-service==0.0.8 +boto3==1.21.21 From d5e7a0b8d3bccf9ee64a624f00e6fe45fe0f91e9 Mon Sep 17 00:00:00 2001 From: Geoffrey Aldebert Date: Fri, 10 Jun 2022 20:54:20 +0200 Subject: [PATCH 03/14] Add consume kafka command --- csvapi/cli.py | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/csvapi/cli.py b/csvapi/cli.py index 6d5675c..ec9bec3 100644 --- a/csvapi/cli.py +++ b/csvapi/cli.py @@ -1,8 +1,11 @@ import click import ssl - +import os from click_default_group import DefaultGroup +from udata_event_service.consumer import consume_kafka +from csvapi.consumer import run_process_message + from csvapi.webservice import app RESPONSE_TIMEOUT = 5 * 60 # in seconds @@ -50,3 +53,15 @@ def serve(dbs, host, port, debug, reload, cache, max_workers, ssl_cert, ssl_key) 'RESPONSE_TIMEOUT': RESPONSE_TIMEOUT, }) app.run(host=host, port=port, debug=debug, use_reloader=reload, ssl=ssl_context) + + +@cli.command() +def consume() -> None: + consume_kafka( + kafka_uri=f'{os.environ.get("KAFKA_HOST", "localhost")}:{os.environ.get("KAFKA_PORT", "9092")}', + group_id="csvapi", + topics=os.environ.get( + "TOPICS", ["resource.analysed"] + ), + message_processing_func=run_process_message, + ) From ff7bffa351a0b6b500a70132a6ba325edde61776 Mon Sep 17 00:00:00 2001 From: Geoffrey Aldebert Date: Fri, 10 Jun 2022 20:55:21 +0200 Subject: [PATCH 04/14] Add kafka integration, read message, analysis, pandas profiling minimal, store to sqlite, modify api with new infos --- csvapi/consumer.py | 168 +++++++++++++++++++++++++++++++++++++++++ csvapi/parser.py | 25 ++++-- csvapi/parseview.py | 33 +++++++- csvapi/profileview.py | 38 +++++++++- csvapi/setup_logger.py | 3 + csvapi/tableview.py | 141 ++++++++++++++++++++++++++++++++++ csvapi/type_tester.py | 43 +++++++++++ csvapi/utils.py | 21 +++++- 8 files changed, 457 insertions(+), 15 deletions(-) create mode 100644 csvapi/consumer.py create mode 100644 csvapi/setup_logger.py diff --git a/csvapi/consumer.py b/csvapi/consumer.py new file mode 100644 index 0000000..de640ac --- /dev/null +++ b/csvapi/consumer.py @@ -0,0 +1,168 @@ +import logging +import asyncio +from csvapi.parseview import ParseView +from csvapi.profileview import ProfileView +from csvapi.setup_logger import logger +import requests + +from config import MINIO_URL, MINIO_USER, MINIO_PASSWORD, DB_ROOT_DIR + +from csvapi.utils import get_hash, create_connection + +import boto3 +from botocore.client import Config, ClientError +import json +from datetime import datetime +import pandas as pd + +def run_process_message(key: str, data: dict, topic: str) -> None: + asyncio.get_event_loop().run_until_complete(process_message(key, data, topic)) + +async def process_message(key: str, message: dict, topic: str) -> None: + # Get url + # Should think if we keep that + #r = requests.get('https://www.data.gouv.fr/api/1/datasets/{}/resources/{}'.format(message['meta']['dataset_id'], key)) + #url = r.json()['url'] + url = 'https://www.data.gouv.fr/fr/datasets/r/{}'.format(key) + logger.info(url) + urlhash = get_hash(url) + + # Connect to minio + s3_client = boto3.client( + "s3", + endpoint_url=MINIO_URL, + aws_access_key_id=MINIO_USER, + aws_secret_access_key=MINIO_PASSWORD, + config=Config(signature_version="s3v4"), + ) + + try: + s3_client.head_bucket(Bucket=message['value']['bucket']) + logger.info('ok bucket') + except ClientError as e: + logger.error(e) + logger.error( + "Bucket {} does not exist or credentials are invalid".format( + message['value']['bucket'] + ) + ) + return + + # Load csv-detective report + response = s3_client.get_object(Bucket = message['value']['bucket'], Key = message['value']['report']) + content = response['Body'] + csv_detective_report = json.loads(content.read()) + + # Parse file and store it to sqlite + parseViewInstance = ParseView() + await parseViewInstance.parse_from_consumer( + parseViewInstance, + url=url, + urlhash=urlhash, + csv_detective_report = csv_detective_report + ) + + # Profile file + profileViewInstance = ProfileView() + profile_report = await profileViewInstance.get_minimal_profile( + profileViewInstance, + urlhash=urlhash, + csv_detective_report = csv_detective_report + ) + + # Save to sql + conn = create_connection(DB_ROOT_DIR+'/'+urlhash+'.db') + #c = conn.cursor() + + general_infos = [ + { + 'encoding': csv_detective_report['encoding'], + 'separator': csv_detective_report['separator'], + 'header_row_idx': csv_detective_report['header_row_idx'], + 'total_lines': profile_report['table']['n'], + 'nb_columns': profile_report['table']['n_var'], + 'nb_cells_missing': profile_report['table']['n_cells_missing'], + 'nb_vars_with_missing': profile_report['table']['n_vars_with_missing'], + 'nb_vars_all_missing': profile_report['table']['n_vars_all_missing'], + 'date_last_check': datetime.today().strftime('%Y-%m-%d') + } + ] + df = pd.DataFrame(general_infos) + df.to_sql('general_infos',con=conn, if_exists='replace', index=False) + + columns_infos = [] + categorical_infos = [] + top_infos = [] + numeric_infos = [] + numeric_plot_infos = [] + for col in profile_report['variables']: + column_info = {} + column_info['name'] = col + column_info['format'] = csv_detective_report['columns'][col]['format'] + column_info['nb_distinct'] = profile_report['variables'][col]['n_distinct'] + column_info['is_unique'] = profile_report['variables'][col]['is_unique'] + column_info['nb_unique'] = profile_report['variables'][col]['n_unique'] + column_info['type'] = profile_report['variables'][col]['type'] + column_info['nb_missing'] = profile_report['variables'][col]['n_missing'] + column_info['count'] = profile_report['variables'][col]['count'] + columns_infos.append(column_info) + logger.info(column_info) + + if((profile_report['variables'][col]['type'] == 'Categorical') & (len(profile_report['variables'][col]['value_counts_without_nan']) < 10)): + for cat in profile_report['variables'][col]['value_counts_without_nan']: + categorical_info = {} + categorical_info['column'] = col + categorical_info['value'] = cat + categorical_info['nb'] = profile_report['variables'][col]['value_counts_without_nan'][cat] + categorical_infos.append(categorical_info) + + if(profile_report['variables'][col]['type'] == 'Numeric'): + numeric_info = {} + numeric_info['column'] = col + numeric_info['mean'] = profile_report['variables'][col]['mean'] + numeric_info['std'] = profile_report['variables'][col]['std'] + numeric_info['min'] = profile_report['variables'][col]['min'] + numeric_info['max'] = profile_report['variables'][col]['max'] + numeric_infos.append(numeric_info) + for i in range(len(profile_report['variables'][col]['histogram']['bin_edges'])): + numeric_plot_info = {} + numeric_plot_info['column'] = col + numeric_plot_info['value'] = profile_report['variables'][col]['histogram']['bin_edges'][i] + numeric_plot_info['type'] = 'bin_edges' + numeric_plot_infos.append(numeric_plot_info) + + for i in range(len(profile_report['variables'][col]['histogram']['counts'])): + numeric_plot_info = {} + numeric_plot_info['column'] = col + numeric_plot_info['value'] = profile_report['variables'][col]['histogram']['counts'][i] + numeric_plot_info['type'] = 'counts' + numeric_plot_infos.append(numeric_plot_info) + + + cpt = 0 + for top in profile_report['variables'][col]['value_counts_without_nan']: + if (cpt < 10): + top_info = {} + top_info['column'] = col + top_info['value'] = top + top_info['nb'] = profile_report['variables'][col]['value_counts_without_nan'][top] + top_infos.append(top_info) + cpt = cpt + 1 + + df = pd.DataFrame(columns_infos) + df.to_sql('column_infos',con=conn, if_exists='replace', index=False) + df = pd.DataFrame(categorical_infos) + df.to_sql('categorical_infos',con=conn, if_exists='replace', index=False) + df = pd.DataFrame(top_infos) + df.to_sql('top_infos',con=conn, if_exists='replace', index=False) + df = pd.DataFrame(numeric_infos) + df.to_sql('numeric_infos',con=conn, if_exists='replace', index=False) + df = pd.DataFrame(numeric_plot_infos) + df.to_sql('numeric_plot_infos',con=conn, if_exists='replace', index=False) + conn.commit() + + print('ok') + + # Consolider detection de type pandas profiling + # on dirait qu'il ne comprend pas le dtype à la lecture (notamment sur siren) + diff --git a/csvapi/parser.py b/csvapi/parser.py index ed41a63..43eab8f 100644 --- a/csvapi/parser.py +++ b/csvapi/parser.py @@ -7,6 +7,7 @@ from csvapi.utils import get_db_info from csvapi.type_tester import agate_tester + SNIFF_LIMIT = 4096 CSV_FILETYPES = ('text/plain', 'application/csv') @@ -21,14 +22,21 @@ def detect_encoding(filepath): return chardet.detect(f.read()).get('encoding') -def from_csv(filepath, encoding='utf-8', sniff_limit=SNIFF_LIMIT): +def from_csv(filepath, encoding='utf-8', sniff_limit=SNIFF_LIMIT, agate_types=None): """Try first w/ sniffing and then w/o sniffing if it fails, and then again by forcing ';' delimiter w/o sniffing""" - kwargs = { - 'sniff_limit': sniff_limit, - 'encoding': encoding, - 'column_types': agate_tester() - } + if not agate_types: + kwargs = { + 'sniff_limit': sniff_limit, + 'encoding': encoding, + 'column_types': agate_tester() + } + else: + kwargs = { + 'sniff_limit': sniff_limit, + 'encoding': encoding, + 'column_types': agate_types + } try: return agate.Table.from_csv(filepath, **kwargs) except ValueError: @@ -53,7 +61,7 @@ def to_sql(table, urlhash, storage): table.to_sql(db_info['dsn'], db_info['db_name'], overwrite=True) -def parse(filepath, urlhash, storage, encoding=None, sniff_limit=SNIFF_LIMIT): +def parse(filepath, urlhash, storage, encoding=None, sniff_limit=SNIFF_LIMIT, agate_types=None): file_type = detect_type(filepath) if 'application/vnd.ms-excel' in file_type: table = from_excel(filepath) @@ -61,7 +69,8 @@ def parse(filepath, urlhash, storage, encoding=None, sniff_limit=SNIFF_LIMIT): table = from_excel(filepath, xlsx=True) elif any([supported in file_type for supported in CSV_FILETYPES]): encoding = detect_encoding(filepath) if not encoding else encoding - table = from_csv(filepath, encoding=encoding, sniff_limit=sniff_limit) + table = from_csv(filepath, encoding=encoding, sniff_limit=sniff_limit, agate_types=agate_types) else: raise Exception(f'Unsupported file type {file_type}') return to_sql(table, urlhash, storage) + diff --git a/csvapi/parseview.py b/csvapi/parseview.py index 70a1f42..5300927 100644 --- a/csvapi/parseview.py +++ b/csvapi/parseview.py @@ -2,6 +2,7 @@ import tempfile import aiohttp +from sqlalchemy import column import validators from quart import request, jsonify, current_app as app @@ -11,11 +12,39 @@ from csvapi.parser import parse from csvapi.utils import already_exists, get_hash +from csvapi.setup_logger import logger +from csvapi.type_tester import convert_types +import os +from config import DB_ROOT_DIR, CSV_SNIFF_LIMIT, MAX_FILE_SIZE + class ParseView(MethodView): @staticmethod - async def do_parse(url, urlhash, encoding, storage, logger, sniff_limit, max_file_size): + async def parse_from_consumer(self, url: str, urlhash: str, csv_detective_report: dict) -> None: + logger.info('Started') + storage = DB_ROOT_DIR + + column_types = [] + for col in csv_detective_report['columns']: + column_types.append(csv_detective_report['columns'][col]['python_type']) + agate_types = convert_types(column_types) + + logger.info(column_types) + await self.do_parse(url=url, + urlhash=urlhash, + encoding=csv_detective_report['encoding'], + storage=storage, + logger=logger, + sniff_limit=CSV_SNIFF_LIMIT, + max_file_size=MAX_FILE_SIZE, + agate_types=agate_types + ) + logger.info('Finished') + + + @staticmethod + async def do_parse(url, urlhash, encoding, storage, logger, sniff_limit, max_file_size, agate_types = None): logger.debug('* do_parse %s (%s)', urlhash, url) tmp = tempfile.NamedTemporaryFile(delete=False) chunk_count = 0 @@ -35,7 +64,7 @@ async def do_parse(url, urlhash, encoding, storage, logger, sniff_limit, max_fil tmp.close() logger.debug('* Downloaded %s', urlhash) logger.debug('* Parsing %s...', urlhash) - parse(tmp.name, urlhash, storage, encoding=encoding, sniff_limit=sniff_limit) + parse(tmp.name, urlhash, storage, encoding=encoding, sniff_limit=sniff_limit, agate_types=agate_types) logger.debug('* Parsed %s', urlhash) finally: logger.debug('Removing tmp file: %s', tmp.name) diff --git a/csvapi/profileview.py b/csvapi/profileview.py index c9d1c5a..55565dd 100644 --- a/csvapi/profileview.py +++ b/csvapi/profileview.py @@ -10,16 +10,26 @@ from csvapi.errors import APIError from csvapi.utils import get_db_info +from csvapi.type_tester import convert_python_types + from quart import current_app as app +import json +from csvapi.setup_logger import logger class ProfileView(MethodView): - def make_profile(self, db_info): + def get_dataframe(self, db_info, dtype=None): dsn = 'file:{}?immutable=1'.format(db_info['db_path']) conn = sqlite3.connect(dsn) sql = 'SELECT * FROM [{}]'.format(db_info['table_name']) - df = pd.read_sql(sql, con=conn) + logger.info(dtype) + df = pd.read_sql_query(sql, con=conn, dtype=dtype) + return df + + def make_profile(self, db_info): + df = self.get_dataframe(db_info) + if(app.config['PANDAS_PROFILING_CONFIG_MIN']): profile = ProfileReport(df, config_file="profiling-minimal.yml") else: @@ -42,3 +52,27 @@ async def get(self, urlhash): raise APIError('Error selecting data', status=400, payload=dict(details=str(e))) return await send_from_directory(path.parent, path.name) + + + async def get_minimal_profile(self, url: str, urlhash: str, csv_detective_report: dict) -> None: + db_info = get_db_info(urlhash) + logger.info(urlhash) + p = Path(db_info['db_path']) + if not p.exists(): + raise APIError('Database has probably been removed or does not exist yet.', status=404) + + path = Path(db_info['profile_path']) + + if not path.exists(): + try: + + python_types = convert_python_types(csv_detective_report['columns']) + logger.info(python_types) + df = self.get_dataframe(db_info, dtype=python_types) + profile = ProfileReport(df, minimal=True, vars=dict(num={"low_categorical_threshold": 0}), plot=dict(histogram={"bins": 10})) + profile_report = json.loads(profile.to_json()) + return profile_report + except (sqlite3.OperationalError, sqlite3.IntegrityError) as e: + raise APIError('Error selecting data', status=400, payload=dict(details=str(e))) + + return await send_from_directory(path.parent, path.name) diff --git a/csvapi/setup_logger.py b/csvapi/setup_logger.py new file mode 100644 index 0000000..61ed41b --- /dev/null +++ b/csvapi/setup_logger.py @@ -0,0 +1,3 @@ +import logging +logging.basicConfig(level=logging.INFO) +logger = logging.getLogger('test') diff --git a/csvapi/tableview.py b/csvapi/tableview.py index 9a75498..5d864e2 100644 --- a/csvapi/tableview.py +++ b/csvapi/tableview.py @@ -157,13 +157,154 @@ async def get(self, urlhash): else: raise APIError(f"Unknown _shape: {_shape}", status=400) + general_infos = await self.general_infos(db_info) + columns_infos = await self.columns_infos(db_info) + res = { 'ok': True, 'query_ms': (end - start) * 1000, 'rows': rows, 'columns': data['columns'], + 'general_infos': general_infos, + 'columns_infos': columns_infos } if data.get('total'): res['total'] = data['total'] return jsonify(res) + + async def general_infos(self, db_info): + params = {} + sql = 'SELECT count(*) FROM sqlite_master WHERE type=\'table\' AND name=\'general_infos\'' + rows, description = await self.execute( + sql, db_info, params=params + ) + if(rows[0][0] != 0): + sql = 'SELECT * FROM general_infos' + rows, description = await self.execute( + sql, db_info, params=params + ) + columns = [r[0] for r in description] + res = {} + cpt = 0 + for col in columns: + res[col] = rows[0][cpt] + cpt = cpt + 1 + + return res + else: + return {} + + + async def columns_infos(self, db_info): + params = {} + sql = 'SELECT count(*) FROM sqlite_master WHERE type=\'table\' AND name=\'columns_infos\'' + rows, description = await self.execute( + sql, db_info, params=params + ) + if(rows[0][0] != 0): + sql = 'SELECT * FROM columns_infos' + rows, description = await self.execute( + sql, db_info, params=params + ) + columns = [r[0] for r in description] + + res = {} + for row in rows: + cpt = 1 + res[row[0]] = {} + for col in columns[1:]: + res[row[0]][col] = row[cpt] + cpt = cpt + 1 + + res = await self.top_and_categorical_infos(db_info, res, 'top_infos') + res = await self.top_and_categorical_infos(db_info, res, 'categorical_infos') + res = await self.numeric_infos(db_info, res) + res = await self.numeric_plot_infos(db_info, res) + return res + else: + return {} + + async def top_and_categorical_infos(self, db_info, res, table_name): + params = {} + sql = 'SELECT count(*) FROM sqlite_master WHERE type=\'table\' AND name=\'{}\''.format(table_name) + rows, description = await self.execute( + sql, db_info, params=params + ) + if(rows[0][0] != 0): + sql = 'SELECT * FROM {}'.format(table_name) + rows, description = await self.execute( + sql, db_info, params=params + ) + + for row in rows: + if(table_name not in res[row[0]]): + res[row[0]][table_name] = [] + inter = {} + inter['value'] = row[1] + inter['count'] = row[2] + res[row[0]][table_name].append(inter) + + return res + else: + for col in res: + res[col][table_name] = {} + return res + + async def numeric_infos(self, db_info, res): + params = {} + sql = 'SELECT count(*) FROM sqlite_master WHERE type=\'table\' AND name=\'numeric_infos\'' + rows, description = await self.execute( + sql, db_info, params=params + ) + if(rows[0][0] != 0): + sql = 'SELECT * FROM {}'.format('numeric_infos') + rows, description = await self.execute( + sql, db_info, params=params + ) + + for row in rows: + if('numeric_infos' not in res[row[0]]): + res[row[0]]['numeric_infos'] = {} + + res[row[0]]['numeric_infos']['mean'] = row[1] + res[row[0]]['numeric_infos']['std'] = row[2] + res[row[0]]['numeric_infos']['min'] = row[3] + res[row[0]]['numeric_infos']['max'] = row[4] + + return res + else: + for col in res: + res[col]['numeric_infos'] = {} + return res + + async def numeric_plot_infos(self, db_info, res): + params = {} + sql = 'SELECT count(*) FROM sqlite_master WHERE type=\'table\' AND name=\'numeric_plot_infos\'' + rows, description = await self.execute( + sql, db_info, params=params + ) + if(rows[0][0] != 0): + sql = 'SELECT * FROM {}'.format('numeric_plot_infos') + rows, description = await self.execute( + sql, db_info, params=params + ) + + for row in rows: + if('numeric_plot_infos' not in res[row[0]]): + res[row[0]]['numeric_plot_infos'] = {} + if('counts' not in res[row[0]]['numeric_plot_infos']): + res[row[0]]['numeric_plot_infos']['counts'] = [] + if('bin_edges' not in res[row[0]]['numeric_plot_infos']): + res[row[0]]['numeric_plot_infos']['bin_edges'] = [] + if(row[2] == 'counts'): + res[row[0]]['numeric_plot_infos']['counts'].append(row[1]) + if(row[2] == 'bin_edges'): + res[row[0]]['numeric_plot_infos']['bin_edges'].append(row[1]) + + return res + else: + for col in res: + res[col]['numeric_plot_infos'] = {} + return res + diff --git a/csvapi/type_tester.py b/csvapi/type_tester.py index 1f6ad6b..70c67e3 100644 --- a/csvapi/type_tester.py +++ b/csvapi/type_tester.py @@ -17,6 +17,7 @@ from stdnum.fr.siren import is_valid as is_valid_siren from stdnum.fr.siret import is_valid as is_valid_siret +from csvapi.setup_logger import logger class Time(DataType): # Detect an hour minute string. @@ -72,3 +73,45 @@ def agate_tester(): Text(), ] ) + +def convert_types(column_types): + convert_dict = { + 'string': Text(), + 'int': Number(), + 'float': Number(), + 'number': Number(), + 'date': Date(), + 'bool': Boolean(), + 'geopoint': Text(), + 'geojson': Text(), + 'any': Text(), + 'year': Text(), + 'datetime': DateTime(), + 'time': Time() + } + agate_types = [] + for ct in column_types: + agate_types.append(convert_dict[ct]) + + return agate_types + +def convert_python_types(columns): + convert_dict = { + 'string': str, + 'int': int, + 'float': float, + 'number': float, + 'date': str, + 'bool': bool, + 'geopoint': str, + 'geojson': str, + 'any': str, + 'year': str, + 'datetime': str, + 'time': str + } + python_types = {} + for col in columns: + python_types[col] = convert_dict[columns[col]['python_type']] + + return python_types \ No newline at end of file diff --git a/csvapi/utils.py b/csvapi/utils.py index 09ec690..548adb5 100644 --- a/csvapi/utils.py +++ b/csvapi/utils.py @@ -4,13 +4,22 @@ from quart import current_app as app +from config import PROFILES_ROOT_DIR, DB_ROOT_DIR + +import sqlite3 + executor = None def get_db_info(urlhash, storage=None): - # app.config not thread safe, sometimes we need to pass storage directly - db_storage = storage or app.config['DB_ROOT_DIR'] - profile_storage = app.config['PROFILES_ROOT_DIR'] + if(app): + # app.config not thread safe, sometimes we need to pass storage directly + db_storage = storage or app.config['DB_ROOT_DIR'] + profile_storage = app.config['PROFILES_ROOT_DIR'] + else: + db_storage = DB_ROOT_DIR + profile_storage = PROFILES_ROOT_DIR + db_path = f"{db_storage}/{urlhash}.db" profile_path = f"{profile_storage}/{urlhash}.html" return { @@ -35,3 +44,9 @@ def already_exists(urlhash): if not cache_enabled: return False return Path(get_db_info(urlhash)['db_path']).exists() + + +def create_connection(db_file): + conn = None + conn = sqlite3.connect(db_file) + return conn From 766246a95ec5f19aac65ce31a87a44c48b4f5e55 Mon Sep 17 00:00:00 2001 From: Geoffrey Aldebert Date: Fri, 17 Jun 2022 14:23:31 +0200 Subject: [PATCH 05/14] Updates --- csvapi/consumer.py | 298 ++++++++++++++++++++++-------------------- csvapi/parser.py | 13 ++ csvapi/parseview.py | 4 - csvapi/profileview.py | 9 +- csvapi/tableview.py | 2 +- csvapi/type_tester.py | 6 +- 6 files changed, 180 insertions(+), 152 deletions(-) diff --git a/csvapi/consumer.py b/csvapi/consumer.py index de640ac..05743ad 100644 --- a/csvapi/consumer.py +++ b/csvapi/consumer.py @@ -23,146 +23,162 @@ async def process_message(key: str, message: dict, topic: str) -> None: # Should think if we keep that #r = requests.get('https://www.data.gouv.fr/api/1/datasets/{}/resources/{}'.format(message['meta']['dataset_id'], key)) #url = r.json()['url'] - url = 'https://www.data.gouv.fr/fr/datasets/r/{}'.format(key) - logger.info(url) - urlhash = get_hash(url) - - # Connect to minio - s3_client = boto3.client( - "s3", - endpoint_url=MINIO_URL, - aws_access_key_id=MINIO_USER, - aws_secret_access_key=MINIO_PASSWORD, - config=Config(signature_version="s3v4"), - ) - - try: - s3_client.head_bucket(Bucket=message['value']['bucket']) - logger.info('ok bucket') - except ClientError as e: - logger.error(e) - logger.error( - "Bucket {} does not exist or credentials are invalid".format( - message['value']['bucket'] + if((message is not None) & (message['service'] == 'csvdetective')): + try: + url = 'https://www.data.gouv.fr/fr/datasets/r/{}'.format(key) + urlhash = get_hash(url) + logger.info(urlhash) + + # Connect to minio + s3_client = boto3.client( + "s3", + endpoint_url=MINIO_URL, + aws_access_key_id=MINIO_USER, + aws_secret_access_key=MINIO_PASSWORD, + config=Config(signature_version="s3v4"), + ) + + try: + s3_client.head_bucket(Bucket=message['value']['bucket']) + except ClientError as e: + logger.error(e) + logger.error( + "Bucket {} does not exist or credentials are invalid".format( + message['value']['bucket'] + ) + ) + return + + # Load csv-detective report + response = s3_client.get_object(Bucket = message['value']['bucket'], Key = message['value']['key']) + content = response['Body'] + csv_detective_report = json.loads(content.read()) + + # Parse file and store it to sqlite + parseViewInstance = ParseView() + await parseViewInstance.parse_from_consumer( + parseViewInstance, + url=url, + urlhash=urlhash, + csv_detective_report = csv_detective_report + ) + + # Profile file + profileViewInstance = ProfileView() + profile_report = await profileViewInstance.get_minimal_profile( + profileViewInstance, + urlhash=urlhash, + csv_detective_report = csv_detective_report ) - ) - return - - # Load csv-detective report - response = s3_client.get_object(Bucket = message['value']['bucket'], Key = message['value']['report']) - content = response['Body'] - csv_detective_report = json.loads(content.read()) - - # Parse file and store it to sqlite - parseViewInstance = ParseView() - await parseViewInstance.parse_from_consumer( - parseViewInstance, - url=url, - urlhash=urlhash, - csv_detective_report = csv_detective_report - ) - - # Profile file - profileViewInstance = ProfileView() - profile_report = await profileViewInstance.get_minimal_profile( - profileViewInstance, - urlhash=urlhash, - csv_detective_report = csv_detective_report - ) - - # Save to sql - conn = create_connection(DB_ROOT_DIR+'/'+urlhash+'.db') - #c = conn.cursor() - - general_infos = [ - { - 'encoding': csv_detective_report['encoding'], - 'separator': csv_detective_report['separator'], - 'header_row_idx': csv_detective_report['header_row_idx'], - 'total_lines': profile_report['table']['n'], - 'nb_columns': profile_report['table']['n_var'], - 'nb_cells_missing': profile_report['table']['n_cells_missing'], - 'nb_vars_with_missing': profile_report['table']['n_vars_with_missing'], - 'nb_vars_all_missing': profile_report['table']['n_vars_all_missing'], - 'date_last_check': datetime.today().strftime('%Y-%m-%d') - } - ] - df = pd.DataFrame(general_infos) - df.to_sql('general_infos',con=conn, if_exists='replace', index=False) - - columns_infos = [] - categorical_infos = [] - top_infos = [] - numeric_infos = [] - numeric_plot_infos = [] - for col in profile_report['variables']: - column_info = {} - column_info['name'] = col - column_info['format'] = csv_detective_report['columns'][col]['format'] - column_info['nb_distinct'] = profile_report['variables'][col]['n_distinct'] - column_info['is_unique'] = profile_report['variables'][col]['is_unique'] - column_info['nb_unique'] = profile_report['variables'][col]['n_unique'] - column_info['type'] = profile_report['variables'][col]['type'] - column_info['nb_missing'] = profile_report['variables'][col]['n_missing'] - column_info['count'] = profile_report['variables'][col]['count'] - columns_infos.append(column_info) - logger.info(column_info) - - if((profile_report['variables'][col]['type'] == 'Categorical') & (len(profile_report['variables'][col]['value_counts_without_nan']) < 10)): - for cat in profile_report['variables'][col]['value_counts_without_nan']: - categorical_info = {} - categorical_info['column'] = col - categorical_info['value'] = cat - categorical_info['nb'] = profile_report['variables'][col]['value_counts_without_nan'][cat] - categorical_infos.append(categorical_info) - - if(profile_report['variables'][col]['type'] == 'Numeric'): - numeric_info = {} - numeric_info['column'] = col - numeric_info['mean'] = profile_report['variables'][col]['mean'] - numeric_info['std'] = profile_report['variables'][col]['std'] - numeric_info['min'] = profile_report['variables'][col]['min'] - numeric_info['max'] = profile_report['variables'][col]['max'] - numeric_infos.append(numeric_info) - for i in range(len(profile_report['variables'][col]['histogram']['bin_edges'])): - numeric_plot_info = {} - numeric_plot_info['column'] = col - numeric_plot_info['value'] = profile_report['variables'][col]['histogram']['bin_edges'][i] - numeric_plot_info['type'] = 'bin_edges' - numeric_plot_infos.append(numeric_plot_info) - - for i in range(len(profile_report['variables'][col]['histogram']['counts'])): - numeric_plot_info = {} - numeric_plot_info['column'] = col - numeric_plot_info['value'] = profile_report['variables'][col]['histogram']['counts'][i] - numeric_plot_info['type'] = 'counts' - numeric_plot_infos.append(numeric_plot_info) - - - cpt = 0 - for top in profile_report['variables'][col]['value_counts_without_nan']: - if (cpt < 10): - top_info = {} - top_info['column'] = col - top_info['value'] = top - top_info['nb'] = profile_report['variables'][col]['value_counts_without_nan'][top] - top_infos.append(top_info) - cpt = cpt + 1 - - df = pd.DataFrame(columns_infos) - df.to_sql('column_infos',con=conn, if_exists='replace', index=False) - df = pd.DataFrame(categorical_infos) - df.to_sql('categorical_infos',con=conn, if_exists='replace', index=False) - df = pd.DataFrame(top_infos) - df.to_sql('top_infos',con=conn, if_exists='replace', index=False) - df = pd.DataFrame(numeric_infos) - df.to_sql('numeric_infos',con=conn, if_exists='replace', index=False) - df = pd.DataFrame(numeric_plot_infos) - df.to_sql('numeric_plot_infos',con=conn, if_exists='replace', index=False) - conn.commit() - - print('ok') - - # Consolider detection de type pandas profiling - # on dirait qu'il ne comprend pas le dtype à la lecture (notamment sur siren) + # Save to sql + conn = create_connection(DB_ROOT_DIR+'/'+urlhash+'.db') + #c = conn.cursor() + + general_infos = [ + { + 'encoding': csv_detective_report['encoding'], + 'separator': csv_detective_report['separator'], + 'header_row_idx': csv_detective_report['header_row_idx'], + 'total_lines': profile_report['table']['n'], + 'nb_columns': profile_report['table']['n_var'], + 'nb_cells_missing': profile_report['table']['n_cells_missing'], + 'nb_vars_with_missing': profile_report['table']['n_vars_with_missing'], + 'nb_vars_all_missing': profile_report['table']['n_vars_all_missing'], + 'date_last_check': datetime.today().strftime('%Y-%m-%d'), + 'dataset_id': message['meta']['dataset_id'], + 'resource_id': key + } + ] + df = pd.DataFrame(general_infos) + df.to_sql('general_infos',con=conn, if_exists='replace', index=False) + + columns_infos = [] + categorical_infos = [] + top_infos = [] + numeric_infos = [] + numeric_plot_infos = [] + for col in profile_report['variables']: + column_info = {} + column_info['name'] = col + column_info['format'] = csv_detective_report['columns'][col]['format'] + column_info['nb_distinct'] = profile_report['variables'][col]['n_distinct'] + column_info['is_unique'] = profile_report['variables'][col]['is_unique'] + column_info['nb_unique'] = profile_report['variables'][col]['n_unique'] + column_info['type'] = profile_report['variables'][col]['type'] + column_info['nb_missing'] = profile_report['variables'][col]['n_missing'] + column_info['count'] = profile_report['variables'][col]['count'] + columns_infos.append(column_info) + + if(csv_detective_report['columns'][col]['format'] in ['siren', 'siret', 'code_postal', 'code_commune_insee', 'code_departement', 'code_region', 'tel_fr']): + column_info['type'] = 'Categorical' + + if((column_info['type'] == 'Categorical') & (len(profile_report['variables'][col]['value_counts_without_nan']) < 10)): + for cat in profile_report['variables'][col]['value_counts_without_nan']: + categorical_info = {} + categorical_info['column'] = col + categorical_info['value'] = cat + categorical_info['nb'] = profile_report['variables'][col]['value_counts_without_nan'][cat] + categorical_infos.append(categorical_info) + + if(column_info['type'] == 'Numeric'): + numeric_info = {} + numeric_info['column'] = col + numeric_info['mean'] = profile_report['variables'][col]['mean'] + numeric_info['std'] = profile_report['variables'][col]['std'] + numeric_info['min'] = profile_report['variables'][col]['min'] + numeric_info['max'] = profile_report['variables'][col]['max'] + numeric_infos.append(numeric_info) + for i in range(len(profile_report['variables'][col]['histogram']['bin_edges'])): + numeric_plot_info = {} + numeric_plot_info['column'] = col + numeric_plot_info['value'] = profile_report['variables'][col]['histogram']['bin_edges'][i] + numeric_plot_info['type'] = 'bin_edges' + numeric_plot_infos.append(numeric_plot_info) + + for i in range(len(profile_report['variables'][col]['histogram']['counts'])): + numeric_plot_info = {} + numeric_plot_info['column'] = col + numeric_plot_info['value'] = profile_report['variables'][col]['histogram']['counts'][i] + numeric_plot_info['type'] = 'counts' + numeric_plot_infos.append(numeric_plot_info) + + + cpt = 0 + for top in profile_report['variables'][col]['value_counts_without_nan']: + if (cpt < 10): + top_info = {} + top_info['column'] = col + top_info['value'] = top + top_info['nb'] = profile_report['variables'][col]['value_counts_without_nan'][top] + top_infos.append(top_info) + cpt = cpt + 1 + + df = pd.DataFrame(columns_infos) + if(df.shape[0] > 0): + df.to_sql('columns_infos',con=conn, if_exists='replace', index=False) + + df = pd.DataFrame(categorical_infos) + if(df.shape[0] > 0): + df.to_sql('categorical_infos',con=conn, if_exists='replace', index=False) + + df = pd.DataFrame(top_infos) + if(df.shape[0] > 0): + df.to_sql('top_infos',con=conn, if_exists='replace', index=False) + + df = pd.DataFrame(numeric_infos) + if(df.shape[0] > 0): + df.to_sql('numeric_infos',con=conn, if_exists='replace', index=False) + + df = pd.DataFrame(numeric_plot_infos) + if(df.shape[0] > 0): + df.to_sql('numeric_plot_infos',con=conn, if_exists='replace', index=False) + + conn.commit() + + print('ok') + + # Consolider detection de type pandas profiling + # on dirait qu'il ne comprend pas le dtype à la lecture (notamment sur siren) + except: + logger.info('Error with message', message) diff --git a/csvapi/parser.py b/csvapi/parser.py index 43eab8f..51e16da 100644 --- a/csvapi/parser.py +++ b/csvapi/parser.py @@ -7,6 +7,8 @@ from csvapi.utils import get_db_info from csvapi.type_tester import agate_tester +from csvapi.setup_logger import logger + SNIFF_LIMIT = 4096 CSV_FILETYPES = ('text/plain', 'application/csv') @@ -46,6 +48,17 @@ def from_csv(filepath, encoding='utf-8', sniff_limit=SNIFF_LIMIT, agate_types=No except ValueError: kwargs['delimiter'] = ';' return agate.Table.from_csv(filepath, **kwargs) + except agate.CastError: + try: + kwargs = { + 'sniff_limit': sniff_limit, + 'encoding': encoding, + 'column_types': agate_tester() + } + return agate.Table.from_csv(filepath, **kwargs) + except: + logger.error('error casting') + def from_excel(filepath, xlsx=False): diff --git a/csvapi/parseview.py b/csvapi/parseview.py index 5300927..50375ea 100644 --- a/csvapi/parseview.py +++ b/csvapi/parseview.py @@ -22,15 +22,12 @@ class ParseView(MethodView): @staticmethod async def parse_from_consumer(self, url: str, urlhash: str, csv_detective_report: dict) -> None: - logger.info('Started') storage = DB_ROOT_DIR - column_types = [] for col in csv_detective_report['columns']: column_types.append(csv_detective_report['columns'][col]['python_type']) agate_types = convert_types(column_types) - logger.info(column_types) await self.do_parse(url=url, urlhash=urlhash, encoding=csv_detective_report['encoding'], @@ -40,7 +37,6 @@ async def parse_from_consumer(self, url: str, urlhash: str, csv_detective_report max_file_size=MAX_FILE_SIZE, agate_types=agate_types ) - logger.info('Finished') @staticmethod diff --git a/csvapi/profileview.py b/csvapi/profileview.py index 55565dd..f6a60f6 100644 --- a/csvapi/profileview.py +++ b/csvapi/profileview.py @@ -23,8 +23,11 @@ def get_dataframe(self, db_info, dtype=None): dsn = 'file:{}?immutable=1'.format(db_info['db_path']) conn = sqlite3.connect(dsn) sql = 'SELECT * FROM [{}]'.format(db_info['table_name']) - logger.info(dtype) - df = pd.read_sql_query(sql, con=conn, dtype=dtype) + try: + df = pd.read_sql_query(sql, con=conn, dtype=dtype) + except: + df = pd.read_sql_query(sql, con=conn) + logger.info('problem with python types') return df def make_profile(self, db_info): @@ -56,7 +59,6 @@ async def get(self, urlhash): async def get_minimal_profile(self, url: str, urlhash: str, csv_detective_report: dict) -> None: db_info = get_db_info(urlhash) - logger.info(urlhash) p = Path(db_info['db_path']) if not p.exists(): raise APIError('Database has probably been removed or does not exist yet.', status=404) @@ -67,7 +69,6 @@ async def get_minimal_profile(self, url: str, urlhash: str, csv_detective_report try: python_types = convert_python_types(csv_detective_report['columns']) - logger.info(python_types) df = self.get_dataframe(db_info, dtype=python_types) profile = ProfileReport(df, minimal=True, vars=dict(num={"low_categorical_threshold": 0}), plot=dict(histogram={"bins": 10})) profile_report = json.loads(profile.to_json()) diff --git a/csvapi/tableview.py b/csvapi/tableview.py index 5d864e2..8d62775 100644 --- a/csvapi/tableview.py +++ b/csvapi/tableview.py @@ -216,7 +216,7 @@ async def columns_infos(self, db_info): for col in columns[1:]: res[row[0]][col] = row[cpt] cpt = cpt + 1 - + res = await self.top_and_categorical_infos(db_info, res, 'top_infos') res = await self.top_and_categorical_infos(db_info, res, 'categorical_infos') res = await self.numeric_infos(db_info, res) diff --git a/csvapi/type_tester.py b/csvapi/type_tester.py index 70c67e3..f4d863b 100644 --- a/csvapi/type_tester.py +++ b/csvapi/type_tester.py @@ -81,7 +81,8 @@ def convert_types(column_types): 'float': Number(), 'number': Number(), 'date': Date(), - 'bool': Boolean(), + #'bool': Boolean(), + 'bool': Text(), 'geopoint': Text(), 'geojson': Text(), 'any': Text(), @@ -102,7 +103,8 @@ def convert_python_types(columns): 'float': float, 'number': float, 'date': str, - 'bool': bool, + #'bool': bool, + 'bool': str, 'geopoint': str, 'geojson': str, 'any': str, From 937a0204b4b4337a96b97f006e4042d9cbd12223 Mon Sep 17 00:00:00 2001 From: Geoffrey Aldebert Date: Wed, 29 Jun 2022 16:19:14 +0200 Subject: [PATCH 06/14] Add UDATA_INSTANCE_NAME env --- csvapi/cli.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/csvapi/cli.py b/csvapi/cli.py index ec9bec3..ca0623d 100644 --- a/csvapi/cli.py +++ b/csvapi/cli.py @@ -61,7 +61,7 @@ def consume() -> None: kafka_uri=f'{os.environ.get("KAFKA_HOST", "localhost")}:{os.environ.get("KAFKA_PORT", "9092")}', group_id="csvapi", topics=os.environ.get( - "TOPICS", ["resource.analysed"] + "TOPICS", [os.environ.get("UDATA_INSTANCE_NAME", "udata")+".resource.analysed"] ), message_processing_func=run_process_message, ) From 4bf8ba9d4d4cd8f74ed30ac40a92db7b2c38bf1b Mon Sep 17 00:00:00 2001 From: Geoffrey Aldebert Date: Fri, 1 Jul 2022 17:41:46 +0200 Subject: [PATCH 07/14] Remove env variables from config file --- config.py | 5 +---- csvapi/consumer.py | 7 ++++++- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/config.py b/config.py index 40435eb..9e5604a 100644 --- a/config.py +++ b/config.py @@ -14,7 +14,4 @@ # It will also match subdomains # e.g. REFERRERS_FILTER = ['data.gouv.fr'] will match 'demo.data.gouv.fr' REFERRERS_FILTER = None -PANDAS_PROFILING_CONFIG_MIN = False -MINIO_URL= 'http://localhost:9000' -MINIO_USER = 'geoffrey' -MINIO_PASSWORD = 'geoffrey' \ No newline at end of file +PANDAS_PROFILING_CONFIG_MIN = False \ No newline at end of file diff --git a/csvapi/consumer.py b/csvapi/consumer.py index 05743ad..58b17c5 100644 --- a/csvapi/consumer.py +++ b/csvapi/consumer.py @@ -4,8 +4,13 @@ from csvapi.profileview import ProfileView from csvapi.setup_logger import logger import requests +import os -from config import MINIO_URL, MINIO_USER, MINIO_PASSWORD, DB_ROOT_DIR +from config import DB_ROOT_DIR + +MINIO_URL = os.environ.get("MINIO_URL", "http://minio:9000") +MINIO_USER = os.environ.get("MINIO_USER", "minio") +MINIO_PASSWORD = os.environ.get("MINIO_PASSWORD", "password") from csvapi.utils import get_hash, create_connection From dd78bb61d2ab4d5371d0409aadb6bb878d3940b1 Mon Sep 17 00:00:00 2001 From: Geoffrey Aldebert Date: Fri, 1 Jul 2022 18:02:14 +0200 Subject: [PATCH 08/14] Fix reading kafka message --- csvapi/consumer.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/csvapi/consumer.py b/csvapi/consumer.py index 58b17c5..cd103f9 100644 --- a/csvapi/consumer.py +++ b/csvapi/consumer.py @@ -8,7 +8,7 @@ from config import DB_ROOT_DIR -MINIO_URL = os.environ.get("MINIO_URL", "http://minio:9000") +MINIO_URL = os.environ.get("MINIO_URL", "http://localhost:9000") MINIO_USER = os.environ.get("MINIO_USER", "minio") MINIO_PASSWORD = os.environ.get("MINIO_PASSWORD", "password") @@ -29,7 +29,7 @@ async def process_message(key: str, message: dict, topic: str) -> None: #r = requests.get('https://www.data.gouv.fr/api/1/datasets/{}/resources/{}'.format(message['meta']['dataset_id'], key)) #url = r.json()['url'] if((message is not None) & (message['service'] == 'csvdetective')): - try: + #try: url = 'https://www.data.gouv.fr/fr/datasets/r/{}'.format(key) urlhash = get_hash(url) logger.info(urlhash) @@ -44,18 +44,18 @@ async def process_message(key: str, message: dict, topic: str) -> None: ) try: - s3_client.head_bucket(Bucket=message['value']['bucket']) + s3_client.head_bucket(Bucket=message['value']['location']['bucket']) except ClientError as e: logger.error(e) logger.error( "Bucket {} does not exist or credentials are invalid".format( - message['value']['bucket'] + message['value']['location']['bucket'] ) ) return # Load csv-detective report - response = s3_client.get_object(Bucket = message['value']['bucket'], Key = message['value']['key']) + response = s3_client.get_object(Bucket = message['value']['location']['bucket'], Key = message['value']['location']['key']) content = response['Body'] csv_detective_report = json.loads(content.read()) @@ -185,5 +185,5 @@ async def process_message(key: str, message: dict, topic: str) -> None: # Consolider detection de type pandas profiling # on dirait qu'il ne comprend pas le dtype à la lecture (notamment sur siren) - except: - logger.info('Error with message', message) + #except: + # logger.info('Error with message', message) From bdc9a7f0cce3ee51ead384f56bfc0fe8f0460ba5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alexandre=20Bult=C3=A9?= Date: Fri, 12 Aug 2022 10:32:58 +0200 Subject: [PATCH 09/14] Fix bucket location in message --- csvapi/consumer.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/csvapi/consumer.py b/csvapi/consumer.py index cd103f9..e8ea326 100644 --- a/csvapi/consumer.py +++ b/csvapi/consumer.py @@ -28,7 +28,7 @@ async def process_message(key: str, message: dict, topic: str) -> None: # Should think if we keep that #r = requests.get('https://www.data.gouv.fr/api/1/datasets/{}/resources/{}'.format(message['meta']['dataset_id'], key)) #url = r.json()['url'] - if((message is not None) & (message['service'] == 'csvdetective')): + if message is not None and message['service'] == 'csvdetective': #try: url = 'https://www.data.gouv.fr/fr/datasets/r/{}'.format(key) urlhash = get_hash(url) @@ -44,7 +44,7 @@ async def process_message(key: str, message: dict, topic: str) -> None: ) try: - s3_client.head_bucket(Bucket=message['value']['location']['bucket']) + s3_client.head_bucket(Bucket=message['value']['data_location']['bucket']) except ClientError as e: logger.error(e) logger.error( From e0d29703e66ddc42ece1bf6b0d600a52d563503c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alexandre=20Bult=C3=A9?= Date: Fri, 12 Aug 2022 10:34:03 +0200 Subject: [PATCH 10/14] Fix data location in message --- csvapi/consumer.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/csvapi/consumer.py b/csvapi/consumer.py index e8ea326..6c35d2b 100644 --- a/csvapi/consumer.py +++ b/csvapi/consumer.py @@ -55,7 +55,7 @@ async def process_message(key: str, message: dict, topic: str) -> None: return # Load csv-detective report - response = s3_client.get_object(Bucket = message['value']['location']['bucket'], Key = message['value']['location']['key']) + response = s3_client.get_object(Bucket = message['value']['data_location']['bucket'], Key = message['value']['data_location']['key']) content = response['Body'] csv_detective_report = json.loads(content.read()) From 7d0e3715b47a012cc1928e7d1cc24cdeb6240e73 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alexandre=20Bult=C3=A9?= Date: Fri, 12 Aug 2022 13:28:08 +0200 Subject: [PATCH 11/14] Fix report_location --- csvapi/consumer.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/csvapi/consumer.py b/csvapi/consumer.py index 6c35d2b..62d42ef 100644 --- a/csvapi/consumer.py +++ b/csvapi/consumer.py @@ -55,7 +55,7 @@ async def process_message(key: str, message: dict, topic: str) -> None: return # Load csv-detective report - response = s3_client.get_object(Bucket = message['value']['data_location']['bucket'], Key = message['value']['data_location']['key']) + response = s3_client.get_object(Bucket = message['value']['report_location']['bucket'], Key = message['value']['report_location']['key']) content = response['Body'] csv_detective_report = json.loads(content.read()) From da06697b7dc10876f1b924cdedbf8b8d0db02f4a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alexandre=20Bult=C3=A9?= Date: Fri, 26 Aug 2022 11:04:42 +0200 Subject: [PATCH 12/14] Switch to Poetry, upgrade, lint and cleanup (#99) * Switch to poetry - Explicitely upgrade to python >= 3.9 - Upgrade pandas and pandas-profiling * Cleanup - Overhaul CI file - Remove useless files - Update License attribution - Remove obsolete ansible roles * trigger CI * fix local tests on macos * add linting * add linting * upgrade flake8 and pytest * lint all the thingz * fix tests with strict asyncio mode * really really fix the tests * Update README * Use CI template, publish kafka-integration, bump 1.3.0 * poetry update * invalidate cache * CI: cache-prefix param --- .circleci/config.yml | 126 +- .python-version | 1 - .pyup.yml | 8 - CHANGELOG.md | 3 +- LICENSE | 2 +- README.md | 13 +- ansible/roles/csvapi/defaults/main.yml | 27 - ansible/roles/csvapi/handlers/main.yml | 4 - ansible/roles/csvapi/meta/main.yml | 3 - ansible/roles/csvapi/tasks/main.yml | 97 - ansible/roles/csvapi/templates/config.py | 15 - .../roles/csvapi/templates/hypercorn.service | 15 - ansible/roles/csvapi/templates/nginx.conf | 33 - ansible/roles/nginx/defaults/main.yml | 42 - ansible/roles/nginx/files/letsencrypt-hook | 2 - ansible/roles/nginx/handlers/main.yml | 3 - ansible/roles/nginx/meta/main.yml | 14 - ansible/roles/nginx/tasks/main.yml | 46 - .../roles/nginx/templates/conf.d/gzip.conf | 14 - .../nginx/templates/conf.d/security.conf | 43 - .../templates/conf.d/server_names_size.conf | 5 - ansible/roles/nginx/templates/mime.types | 97 - ansible/roles/nginx/templates/nginx.conf | 38 - .../nginx/templates/sites-available/default | 44 - ansible/roles/python36/defaults/main.yml | 10 - ansible/roles/python36/tasks/main.yml | 46 - csvapi/consumer.py | 337 +-- csvapi/parser.py | 8 +- csvapi/parseview.py | 8 +- csvapi/profileview.py | 16 +- csvapi/tableview.py | 42 +- csvapi/type_tester.py | 13 +- csvapi/utils.py | 6 +- csvapi/webservice.py | 2 +- poetry.lock | 2344 +++++++++++++++++ pyproject.toml | 43 + requirements/develop.pip | 5 - requirements/install.pip | 18 - requirements/test.pip | 3 - setup.py | 85 - tasks.py | 101 - tests/test_api.py | 5 +- 42 files changed, 2693 insertions(+), 1094 deletions(-) delete mode 100644 .python-version delete mode 100644 .pyup.yml delete mode 100644 ansible/roles/csvapi/defaults/main.yml delete mode 100644 ansible/roles/csvapi/handlers/main.yml delete mode 100644 ansible/roles/csvapi/meta/main.yml delete mode 100644 ansible/roles/csvapi/tasks/main.yml delete mode 100644 ansible/roles/csvapi/templates/config.py delete mode 100644 ansible/roles/csvapi/templates/hypercorn.service delete mode 100644 ansible/roles/csvapi/templates/nginx.conf delete mode 100644 ansible/roles/nginx/defaults/main.yml delete mode 100644 ansible/roles/nginx/files/letsencrypt-hook delete mode 100644 ansible/roles/nginx/handlers/main.yml delete mode 100644 ansible/roles/nginx/meta/main.yml delete mode 100644 ansible/roles/nginx/tasks/main.yml delete mode 100644 ansible/roles/nginx/templates/conf.d/gzip.conf delete mode 100644 ansible/roles/nginx/templates/conf.d/security.conf delete mode 100644 ansible/roles/nginx/templates/conf.d/server_names_size.conf delete mode 100644 ansible/roles/nginx/templates/mime.types delete mode 100644 ansible/roles/nginx/templates/nginx.conf delete mode 100644 ansible/roles/nginx/templates/sites-available/default delete mode 100644 ansible/roles/python36/defaults/main.yml delete mode 100644 ansible/roles/python36/tasks/main.yml create mode 100644 poetry.lock create mode 100644 pyproject.toml delete mode 100644 requirements/develop.pip delete mode 100644 requirements/install.pip delete mode 100644 requirements/test.pip delete mode 100644 setup.py delete mode 100644 tasks.py diff --git a/.circleci/config.yml b/.circleci/config.yml index f2c0b6b..ba4268c 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -1,106 +1,136 @@ --- -version: 2 +version: 2.1 + +parameters: + python-version: + type: string + default: "3.9" + python-module: + type: string + default: "csvapi" + publish-branch: + type: string + default: "master" + cache-prefix: + type: string + default: "py-cache-v1" jobs: - python: + install: docker: - - image: circleci/python:3.7 + - image: cimg/python:<< pipeline.parameters.python-version >> steps: - checkout - - run: - name: Compute Python dependencies key - command: cat requirements/*.pip > python.deps - run: name: Get the base reference branch command: export BASE_BRANCH=$(base_branch) - restore_cache: keys: - - py-cache-v2-{{ checksum "python.deps" }} - - py-cache-v2-{{ .Branch }} - - py-cache-v2-{{ .Environment.BASE_BRANCH }} + - << pipeline.parameters.cache-prefix >>-{{ arch }}-{{ checksum "poetry.lock" }} + - << pipeline.parameters.cache-prefix >>-{{ arch }}-{{ .Branch }} + - << pipeline.parameters.cache-prefix >>-{{ arch }}-{{ .Environment.BASE_BRANCH }} - run: name: Install python dependencies command: | - python3 -m venv venv - source venv/bin/activate - pip install -r requirements/develop.pip || pip install -r requirements/develop.pip + poetry config virtualenvs.in-project true + poetry install - save_cache: - key: py-cache-v2-{{ checksum "python.deps" }} + key: << pipeline.parameters.cache-prefix >>-{{ arch }}-{{ checksum "poetry.lock" }} paths: - - venv + - .venv - save_cache: - key: py-cache-v2-{{ .Branch }} + key: << pipeline.parameters.cache-prefix >>-{{ arch }}-{{ .Branch }} + paths: + - .venv + - persist_to_workspace: + root: . paths: - - venv + - . + + lint: + docker: + - image: cimg/python:<< pipeline.parameters.python-version >> + steps: + - attach_workspace: + at: . + - run: + name: Lint code + command: poetry run flake8 << pipeline.parameters.python-module >> + + tests: + docker: + - image: cimg/python:<< pipeline.parameters.python-version >> + steps: + - attach_workspace: + at: . - run: name: Run tests command: | - source venv/bin/activate - inv qa test --report + poetry run pytest --junitxml=reports/python/tests.xml -p no:sugar --color=yes - store_test_results: path: reports/python - - store_artifacts: - path: reports/ - destination: reports - - persist_to_workspace: - root: . - paths: - - venv - - dist: + build: docker: - - image: circleci/python:3.7 + - image: cimg/python:<< pipeline.parameters.python-version >> steps: - - checkout - attach_workspace: at: . - run: name: Build a distributable package command: | - source venv/bin/activate - if [[ $CIRCLE_TAG ]]; then inv dist; else inv dist -b $CIRCLE_BUILD_NUM; fi + # Build a wheel release + if [[ $CIRCLE_TAG ]]; then + # This is a tagged release, version has been handled upstream + poetry build + else + # Relies on a dev version like "1.2.1.dev" by default + poetry version $(poetry version -s)$CIRCLE_BUILD_NUM + poetry build + fi - store_artifacts: path: dist - persist_to_workspace: root: . paths: - - dist + - . publish: docker: - - image: circleci/python:3.7 + - image: cimg/python:<< pipeline.parameters.python-version >> steps: - attach_workspace: at: . - - run: - name: Install Twine - command: sudo pip install twine - deploy: name: Publish on PyPI - command: twine upload --username "${PYPI_USERNAME}" --password "${PYPI_PASSWORD}" dist/*.whl + command: | + poetry publish --username "${PYPI_USERNAME}" --password "${PYPI_PASSWORD}" --no-interaction workflows: version: 2 build: jobs: - - python: - filters: - tags: - only: /v[0-9]+(\.[0-9]+)*/ - - dist: + - install + - lint: requires: - - python - filters: - tags: - only: /v[0-9]+(\.[0-9]+)*/ + - install + - tests: + requires: + - install + - build: + requires: + - tests + - lint - publish: requires: - - dist + - build filters: branches: only: - - /v[0-9]+(\.[0-9]+)*/ + # FIXME: remove when merging on master + - kafka-integration + - << pipeline.parameters.publish-branch >> + - /[0-9]+(\.[0-9]+)+/ tags: only: /v[0-9]+(\.[0-9]+)*/ context: org-global diff --git a/.python-version b/.python-version deleted file mode 100644 index c77a7de..0000000 --- a/.python-version +++ /dev/null @@ -1 +0,0 @@ -3.7.9 diff --git a/.pyup.yml b/.pyup.yml deleted file mode 100644 index cc65dbc..0000000 --- a/.pyup.yml +++ /dev/null @@ -1,8 +0,0 @@ -requirements: - - requirements/develop.pip: - update: False - - requirements/install.pip: - update: True - pin: True - - requirements/test.pip: - update: False diff --git a/CHANGELOG.md b/CHANGELOG.md index d63419a..e598416 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,7 +2,8 @@ ## Current (in progress) -- Nothing yet +- [BREAKING] Migrate to python >= 3.9 +- Migrate to poetry ## 1.2.1 (2021-04-29) diff --git a/LICENSE b/LICENSE index fc29780..31ab85c 100644 --- a/LICENSE +++ b/LICENSE @@ -1,4 +1,4 @@ -Copyright 2018 Alexandre Bulté +Copyright 2022 Etalab Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: diff --git a/README.md b/README.md index da51d42..b2cf5ee 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ This tool is used by [data.gouv.fr](https://www.data.gouv.fr) to show a preview ## Installation -Requires Python 3.7+ and a Unix OS with the `file` command available. +Requires Python 3.9+ and a Unix OS with the `file` command available. ```shell python3 -m venv pyenv && . pyenv/bin/activate @@ -16,20 +16,19 @@ pip install csvapi For development: ```shell -pip install -r requirements/develop.pip -pip install -e . +poetry install ``` ## Quickstart ```shell -csvapi serve -h 0.0.0.0 -p 8000 +poetry run csvapi serve -h 0.0.0.0 -p 8000 ``` ## Command line options ```shell -$ csvapi serve --help +$ poetry run csvapi serve --help Usage: csvapi serve [OPTIONS] Options: @@ -245,10 +244,6 @@ By adding `{column}__{comparator}={value}` to the query string, you can filter t You can add multiple filters, they will be joined with a `AND` at the SQL level. -## Production deployment - -Some example [Ansible 2](https://www.ansible.com) roles are [available here](/ansible). - ## Credits Inspired by the excellent [Datasette](https://github.com/simonw/datasette). diff --git a/ansible/roles/csvapi/defaults/main.yml b/ansible/roles/csvapi/defaults/main.yml deleted file mode 100644 index 490528b..0000000 --- a/ansible/roles/csvapi/defaults/main.yml +++ /dev/null @@ -1,27 +0,0 @@ ---- -csvapi__version: 1.0.0 -csvapi__user: csvapi -csvapi__server_name: csvapi.company.com -csvapi__home: /srv/csvapi -csvapi__home_dbs: "{{csvapi__home}}/dbs" -csvapi__config: "{{csvapi__home}}/config.py" -csvapi__nginx_logs: "/var/log/nginx/{{csvapi__server_name}}" -# Define this to a certificate object to enable SSL -csvapi__ssl_certificate: false -csvapi__workers: 3 -csvapi__sentry_dsn: -csvapi__csv_sniff_limit: 4096 -csvapi__max_file_size: 104857600 -csvapi__debug: false -csvapi__allowed_referrers: [] - -hypercorn__user: "{{csvapi__user}}" -hypercorn__app: csvapi.webservice:app -hypercorn__workers: 3 -hypercorn__bind_port: 8080 -hypercorn__bind_host: 127.0.0.1 -hypercorn__log_dir: /var/log/hypercorn -hypercorn__access_log: "{{hypercorn__log_dir}}/{{csvapi__server_name}}.access.log" -hypercorn__error_log: "{{hypercorn__log_dir}}/{{csvapi__server_name}}.error.log" -hypercorn__binary: "{{csvapi__home}}/bin/hypercorn" -hypercorn__limit_no_file: 1024 diff --git a/ansible/roles/csvapi/handlers/main.yml b/ansible/roles/csvapi/handlers/main.yml deleted file mode 100644 index 384415c..0000000 --- a/ansible/roles/csvapi/handlers/main.yml +++ /dev/null @@ -1,4 +0,0 @@ ---- - -- name: restart hypercorn - systemd: name=hypercorn state=restarted daemon_reload={{hypercorn__systemd_service.changed}} diff --git a/ansible/roles/csvapi/meta/main.yml b/ansible/roles/csvapi/meta/main.yml deleted file mode 100644 index da7049d..0000000 --- a/ansible/roles/csvapi/meta/main.yml +++ /dev/null @@ -1,3 +0,0 @@ -dependencies: -- { role: python36, tags: ['python'] } -- { role: nginx, tags: ['nginx'] } diff --git a/ansible/roles/csvapi/tasks/main.yml b/ansible/roles/csvapi/tasks/main.yml deleted file mode 100644 index e2e3c4a..0000000 --- a/ansible/roles/csvapi/tasks/main.yml +++ /dev/null @@ -1,97 +0,0 @@ ---- - -- name: Ensure file command is installed - apt: pkg=file state=present - tags: - - system - - install - -- name: Ensure csvapi user exists - user: name={{csvapi__user}} home={{csvapi__home}} createhome=yes shell=/bin/bash - -- name: Configure virtualenv - become: true - become_user: "{{ csvapi__user }}" - command: virtualenv -p python3.6 {{csvapi__home}} creates={{csvapi__home}}/bin/python - -- name: Install package - become: true - become_user: "{{ csvapi__user }}" - pip: name=csvapi version={{csvapi__version}} virtualenv={{csvapi__home}} - notify: restart hypercorn - -- name: Add csvapi configuration - template: src=config.py dest={{csvapi__config}} - notify: restart hypercorn - tags: - - config - -- name: Ensure .profile is present - file: path={{ csvapi__home }}/.profile state=touch owner={{ csvapi__user }} mode=0644 - tags: - - config - -- name: Set environment variable CSVAPI_CONFIG_FILE - become: true - become_user: "{{ csvapi__user }}" - lineinfile: - dest: "{{csvapi__home}}/.profile" - line: "export CSVAPI_CONFIG_FILE={{ csvapi__config }}" - regexp: "^export CSVAPI_CONFIG_FILE=" - notify: restart hypercorn - tags: - - config - -# should not be needed? But ~/bin was not on the PATH... -- name: Set environment variable PATH - become: true - become_user: "{{ csvapi__user }}" - lineinfile: - dest: "{{csvapi__home}}/.profile" - line: "export PATH={{ csvapi__home }}/bin:$PATH" - regexp: "^export PATH=" - notify: restart hypercorn - tags: - - config - -- name: Create nginx logs directory - file: path={{ csvapi__nginx_logs }} state=directory owner={{ csvapi__user }} group=www-data mode=0775 - tags: - - nginx - -- name: Install nginx site - template: src=nginx.conf dest=/etc/nginx/sites-available/{{ csvapi__server_name }} - notify: restart nginx - tags: - - config - - nginx - -- name: Enable nginx site - file: src=/etc/nginx/sites-available/{{ csvapi__server_name }} dest=/etc/nginx/sites-enabled/{{csvapi__server_name}} state=link - notify: restart nginx - tags: - - config - - nginx - -- name: Create hypercorn logs directory - file: path={{ hypercorn__log_dir }} state=directory owner={{ hypercorn__user }} mode=0775 - notify: restart hypercorn - tags: - - hypercorn - -- name: Create dbs directory - file: path={{ csvapi__home_dbs }} state=directory owner={{ hypercorn__user }} mode=0775 - tags: - - hypercorn - -- name: Install systemd unit files - template: src=hypercorn.service dest=/etc/systemd/system/hypercorn.service - register: hypercorn__systemd_service - notify: restart hypercorn - tags: - - config - -- name: Ensure hypercorn service is started and enabled at boot - systemd: name=hypercorn state=started enabled=yes daemon_reload={{hypercorn__systemd_service.changed}} - tags: - - system diff --git a/ansible/roles/csvapi/templates/config.py b/ansible/roles/csvapi/templates/config.py deleted file mode 100644 index ab43833..0000000 --- a/ansible/roles/csvapi/templates/config.py +++ /dev/null @@ -1,15 +0,0 @@ -DB_ROOT_DIR = "{{csvapi__home_dbs}}" -CSV_CACHE_ENABLED = True -MAX_WORKERS = {{csvapi__workers}} -DEBUG = {{csvapi__debug}} -SENTRY_DSN = "{{csvapi__sentry_dsn}}" -{% if csvapi__ssl_certificate %} -FORCE_SSL = True -{% endif %} -CSV_SNIFF_LIMIT = {{csvapi__csv_sniff_limit}} -MAX_FILE_SIZE = {{csvapi__max_file_size}} -REFERRERS_FILTER = [ -{% for referrer in csvapi__allowed_referrers %} - '{{ referrer }}', -{% endfor %} -] diff --git a/ansible/roles/csvapi/templates/hypercorn.service b/ansible/roles/csvapi/templates/hypercorn.service deleted file mode 100644 index d3451a9..0000000 --- a/ansible/roles/csvapi/templates/hypercorn.service +++ /dev/null @@ -1,15 +0,0 @@ -[Unit] -Description=Hypercorn server -After=network.target - -[Service] -User={{hypercorn__user}} -# access-log and error-log won't work before hypercorn > 0.2.4 -# cf https://gitlab.com/pgjones/hypercorn/commit/cc53383612ede10fb484aec2ab807a6109709fc1 -# in the meantime use journalctl -u hypercorn -ExecStart={{hypercorn__binary}} {{hypercorn__app}} -b {{hypercorn__bind_host}}:{{hypercorn__bind_port}} -k {{hypercorn__workers}} --access-log {{hypercorn__access_log}} --error-log {{hypercorn__error_log}} -Environment=CSVAPI_CONFIG_FILE={{ csvapi__config }} -LimitNOFILE={{hypercorn__limit_no_file }} - -[Install] -WantedBy=multi-user.target diff --git a/ansible/roles/csvapi/templates/nginx.conf b/ansible/roles/csvapi/templates/nginx.conf deleted file mode 100644 index 714321b..0000000 --- a/ansible/roles/csvapi/templates/nginx.conf +++ /dev/null @@ -1,33 +0,0 @@ -## -# NGinx configuration for {{ csvapi__server_name }} -# {{ ansible_managed }} -## - -upstream csvapi { - server 127.0.0.1:{{hypercorn__bind_port}} fail_timeout=0; -} - -server { - listen {% if csvapi__ssl_certificate %}443 ssl{% else %}80{% endif %}; - listen {% if csvapi__ssl_certificate %}[::]:443 ssl{% else %}[::]:80{% endif %}; - - server_name {{csvapi__server_name}}; - - {% if csvapi__ssl_certificate %} - ssl_certificate {{ csvapi__ssl_certificate.chain }}; - ssl_certificate_key {{ csvapi__ssl_certificate.key }}; - {% endif %} - - access_log {{ csvapi__nginx_logs }}/access.log; - error_log {{ csvapi__nginx_logs }}/error.log; - - location / { - proxy_pass http://csvapi; - proxy_redirect off; - proxy_set_header Host $host; - proxy_set_header X-Real-IP $remote_addr; - proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; - proxy_set_header X-Forwarded-Host $server_name; - proxy_set_header X-Forwarded-Proto $scheme; - } -} diff --git a/ansible/roles/nginx/defaults/main.yml b/ansible/roles/nginx/defaults/main.yml deleted file mode 100644 index 3cb8406..0000000 --- a/ansible/roles/nginx/defaults/main.yml +++ /dev/null @@ -1,42 +0,0 @@ ---- -nginx_error_log: /var/log/nginx/error.log -nginx_access_log: /var/log/nginx/access.log -nginx_cache_dir: /var/cache/nginx -nginx_user: www-data -nginx_group: www-data -nginx_worker_processes: auto -nginx_worker_connections: 768 -nginx_server_names_size: 64 -nginx_max_body_size: 2M -nginx_ssl: false -nginx_dhparam_path: /etc/ssl/private/dhparam.pem -nginx_dhparam_size: 2048 -nginx_status_allow: - - 127.0.0.1 - - ::1 -nginx_gzip_min_length: 1000 -nginx_gzip_level: 6 -nginx_gzip_mimes: - - application/atom+xml - - application/javascript - - application/json - - application/ld+json - - application/rss+xml - - application/vnd.ms-fontobject - - application/x-font-ttf - - application/x-javascript - - application/x-web-app-manifest+json - - application/xhtml+xml - - application/xml - - application/xml+rss - - font/opentype - - image/svg+xml - - image/x-icon - - text/css - - text/csv - - text/javascript - - text/plain - - text/x-component - -nginx_backport: false -nginx_default_distribution: "{% if nginx_backport %}{{ ansible_distribution_release }}-backports{% endif%}" diff --git a/ansible/roles/nginx/files/letsencrypt-hook b/ansible/roles/nginx/files/letsencrypt-hook deleted file mode 100644 index f17457e..0000000 --- a/ansible/roles/nginx/files/letsencrypt-hook +++ /dev/null @@ -1,2 +0,0 @@ -#!/bin/bash -systemctl reload nginx diff --git a/ansible/roles/nginx/handlers/main.yml b/ansible/roles/nginx/handlers/main.yml deleted file mode 100644 index 3033fb0..0000000 --- a/ansible/roles/nginx/handlers/main.yml +++ /dev/null @@ -1,3 +0,0 @@ ---- -- name: restart nginx - action: service name=nginx state=restarted diff --git a/ansible/roles/nginx/meta/main.yml b/ansible/roles/nginx/meta/main.yml deleted file mode 100644 index 7969975..0000000 --- a/ansible/roles/nginx/meta/main.yml +++ /dev/null @@ -1,14 +0,0 @@ -galaxy_info: - author: Etalab - description: NGinx base installation - company: Etalab - - license: MIT - - min_ansible_version: 2.4 - - # platforms is a list of platforms, and each platform has a name and a list of versions. - platforms: - - name: Debian - versions: - - 9 diff --git a/ansible/roles/nginx/tasks/main.yml b/ansible/roles/nginx/tasks/main.yml deleted file mode 100644 index a2116d8..0000000 --- a/ansible/roles/nginx/tasks/main.yml +++ /dev/null @@ -1,46 +0,0 @@ ---- -- name: Ensure nginx is installed - apt: pkg=nginx state=present default_release={{ nginx_default_distribution|default(omit) }} - tags: - - system - - install - -- name: Ensure openssl is installed - apt: pkg=openssl state=present - when: nginx_ssl - tags: - - system - - install - -- name: Ensure all folders exists - file: path="/etc/nginx/{{ item }}" state=directory - with_items: - - sites-available - - sites-enabled - - conf.d - -- name: Ensure configuration is set - template: src={{ item.src }} dest=/etc/nginx/{{ item.path }} - with_filetree: - - ../templates - when: item.state == 'file' - loop_control: - label: '{{ item.path }}' - notify: restart nginx - tags: - - config - -- name: Ensure cache dir exists - file: path={{ nginx_cache_dir }} state=directory owner={{ nginx_user }} mode=0700 - -- name: Ensure dhparam is set created - command: openssl dhparam -out {{ nginx_dhparam_path }} {{ nginx_dhparam_size }} creates={{ nginx_dhparam_path }} - when: nginx_ssl - -- name: Ensure service is started at boot - service: name=nginx enabled=yes - -- name: Install Let's Encrypt renewal hook - copy: src=letsencrypt-hook dest={{ letsencrypt__config_root }}/renewal-hooks/deploy/nginx mode=0744 - when: letsencrypt__config_root is defined - notify: restart nginx diff --git a/ansible/roles/nginx/templates/conf.d/gzip.conf b/ansible/roles/nginx/templates/conf.d/gzip.conf deleted file mode 100644 index 491e275..0000000 --- a/ansible/roles/nginx/templates/conf.d/gzip.conf +++ /dev/null @@ -1,14 +0,0 @@ -## -# gzip compression settings -# {{ ansible_managed }} -## -gzip on; -gzip_disable "msie6"; - -gzip_vary on; -gzip_proxied any; -gzip_comp_level {{ nginx_gzip_level }}; -gzip_buffers 16 8k; -gzip_http_version 1.1; -gzip_min_length {{ nginx_gzip_min_length }}; -gzip_types {{ nginx_gzip_mimes|join(' ') }}; diff --git a/ansible/roles/nginx/templates/conf.d/security.conf b/ansible/roles/nginx/templates/conf.d/security.conf deleted file mode 100644 index 3530959..0000000 --- a/ansible/roles/nginx/templates/conf.d/security.conf +++ /dev/null @@ -1,43 +0,0 @@ -## -# Security considerations -# {{ ansible_managed }} -## - -## --- SSL - -# ciphers chosen for forward secrecy and compatibility -# http://blog.ivanristic.com/2013/08/configuring-apache-nginx-and-openssl-for-forward-secrecy.html -# ssl_ciphers 'ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES256-GCM-SHA384:kEDH+AESGCM:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA:ECDHE-ECDSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-RSA-AES256-SHA256:DHE-DSS-AES256-SHA:AES128-GCM-SHA256:AES256-GCM-SHA384:ECDHE-RSA-RC4-SHA:ECDHE-ECDSA-RC4-SHA:RC4-SHA:HIGH:!aNULL:!eNULL:!EXPORT:!DES:!3DES:!MD5:!PSK'; -ssl_prefer_server_ciphers on; -ssl_ciphers EECDH+ECDSA+AESGCM:EECDH+aRSA+AESGCM:EECDH+ECDSA+SHA512:EECDH+ECDSA+SHA384:EECDH+ECDSA+SHA256:EDH+aRSA:EECDH:!aNULL:!eNULL:!LOW:!RC4:!3DES:!MD5:!EXP:!PSK:!SRP:!DSS; -ssl_protocols TLSv1.2 TLSv1.1 TLSv1; -ssl_session_cache shared:TLS:2m; -ssl_session_timeout 5m; - -# OCSP stapling -# Need NGinx >= 1.3.7 -ssl_stapling on; - -# For Diffie Hellman Key Exchange: -# Create dhparam.pem: openssl dhparam -out /etc/ssl/private/dhparam.pem 2048 -ssl_dhparam {{nginx_dhparam_path}}; - -# Hide version header -server_tokens off; - - -## --- HTTP - -# when serving user-supplied content, include a X-Content-Type-Options: nosniff header along with the Content-Type: header, -# to disable content-type sniffing on some browsers. -# https://www.owasp.org/index.php/List_of_useful_HTTP_headers -# currently suppoorted in IE > 8 http://blogs.msdn.com/b/ie/archive/2008/09/02/ie8-security-part-vi-beta-2-update.aspx -# http://msdn.microsoft.com/en-us/library/ie/gg622941(v=vs.85).aspx -# 'soon' on Firefox https://bugzilla.mozilla.org/show_bug.cgi?id=471020 -add_header X-Content-Type-Options nosniff; - -# This header enables the Cross-site scripting (XSS) filter built into most recent web browsers. -# It's usually enabled by default anyway, so the role of this header is to re-enable the filter for -# this particular website if it was disabled by the user. -# https://www.owasp.org/index.php/List_of_useful_HTTP_headers -add_header X-XSS-Protection "1; mode=block"; diff --git a/ansible/roles/nginx/templates/conf.d/server_names_size.conf b/ansible/roles/nginx/templates/conf.d/server_names_size.conf deleted file mode 100644 index bbe2d34..0000000 --- a/ansible/roles/nginx/templates/conf.d/server_names_size.conf +++ /dev/null @@ -1,5 +0,0 @@ -## -# Max server_name size -# {{ ansible_managed }} -## -server_names_hash_bucket_size {{nginx_server_names_size}}; diff --git a/ansible/roles/nginx/templates/mime.types b/ansible/roles/nginx/templates/mime.types deleted file mode 100644 index 5cbdf13..0000000 --- a/ansible/roles/nginx/templates/mime.types +++ /dev/null @@ -1,97 +0,0 @@ -types { - text/html html htm shtml; - text/css css; - text/xml xml rss; - image/gif gif; - image/jpeg jpeg jpg; - application/x-javascript js; - application/atom+xml atom; - - text/csv csv; - text/mathml mml; - text/plain txt; - text/vnd.sun.j2me.app-descriptor jad; - text/vnd.wap.wml wml; - text/x-component htc; - - image/png png; - image/tiff tif tiff; - image/vnd.wap.wbmp wbmp; - image/x-icon ico; - image/x-jng jng; - image/x-ms-bmp bmp; - image/svg+xml svg svgz; - - application/java-archive jar war ear; - application/json json; - application/mac-binhex40 hqx; - application/msword doc; - application/pdf pdf; - application/postscript ps eps ai; - application/tar+gzip tar.gz tgz; - application/tar+bzip tar.bz tbz; - application/tar+xz tar.xz txz; - application/rtf rtf; - application/vnd.geo+json geojson; - application/vnd.google-earth.kml+xml kml; - application/vnd.google-earth.kmz kmz; - application/vnd.ms-excel xls; - application/vnd.ms-powerpoint ppt; - application/vnd.oasis.opendocument.graphics odg; - application/vnd.oasis.opendocument.presentation odp; - application/vnd.oasis.opendocument.spreadsheet ods; - application/vnd.oasis.opendocument.text odt; - application/vnd.openxmlformats-officedocument.presentationml.presentation pptx; - application/vnd.openxmlformats-officedocument.spreadsheetml.sheet xlsx; - application/vnd.openxmlformats-officedocument.wordprocessingml.document docx; - application/vnd.wap.wmlc wmlc; - application/x-7z-compressed 7z; - application/x-cocoa cco; - application/x-java-archive-diff jardiff; - application/x-java-jnlp-file jnlp; - application/x-makeself run; - application/x-perl pl pm; - application/x-pilot prc pdb; - application/x-qgis qgis; - application/x-rar-compressed rar; - application/x-redhat-package-manager rpm; - application/x-sea sea; - application/x-shockwave-flash swf; - application/x-stuffit sit; - application/x-tcl tcl tk; - application/x-x509-ca-cert der pem crt; - application/x-xpinstall xpi; - application/xhtml+xml xhtml; - application/zip zip; - - application/octet-stream bin exe dll; - application/octet-stream deb; - application/octet-stream dmg; - application/octet-stream iso img; - application/octet-stream msi msp msm; - application/ogg ogx; - - audio/midi mid midi kar; - audio/mpeg mpga mpega mp2 mp3 m4a; - audio/ogg oga ogg spx; - audio/x-realaudio ra; - audio/webm weba; - - video/3gpp 3gpp 3gp; - video/mp4 mp4; - video/mpeg mpeg mpg mpe; - video/ogg ogv; - video/quicktime mov; - video/webm webm; - video/x-flv flv; - video/x-mng mng; - video/x-ms-asf asx asf; - video/x-ms-wmv wmv; - video/x-msvideo avi; - - application/x-font-ttf ttf; - font/opentype otf; - application/vnd.ms-fontobject eot; - font/x-woff woff; - font/woff2 woff2; -} diff --git a/ansible/roles/nginx/templates/nginx.conf b/ansible/roles/nginx/templates/nginx.conf deleted file mode 100644 index bfb2a4c..0000000 --- a/ansible/roles/nginx/templates/nginx.conf +++ /dev/null @@ -1,38 +0,0 @@ -## -# NGinx configuration -# {{ ansible_managed }} -## -user {{ nginx_user }}; -worker_processes {{ nginx_worker_processes }}; -pid /run/nginx.pid; - -events { - worker_connections {{ nginx_worker_connections }}; -} - -http { - ## - # Basic Settings - ## - sendfile on; - tcp_nopush on; - tcp_nodelay on; - keepalive_timeout 65; - types_hash_max_size 2048; - client_max_body_size {{nginx_max_body_size}}; - - include /etc/nginx/mime.types; - default_type application/octet-stream; - - ## - # Logging Settings - ## - access_log {{nginx_access_log}}; - error_log {{nginx_error_log}}; - - ## - # Virtual Host Configs - ## - include /etc/nginx/conf.d/*.conf; - include /etc/nginx/sites-enabled/*; -} diff --git a/ansible/roles/nginx/templates/sites-available/default b/ansible/roles/nginx/templates/sites-available/default deleted file mode 100644 index cda7484..0000000 --- a/ansible/roles/nginx/templates/sites-available/default +++ /dev/null @@ -1,44 +0,0 @@ -## -# Default server configuration -# -# {{ ansible_managed }} -## - -server { - listen 80 default_server; - listen [::]:80 default_server; - server_name _; - - {% if nginx_ssl %} - # Only accept HTTPS Traffic - location / { - return 301 https://$host$request_uri; - } - - {% if letsencrypt_webroot_path is defined %} - location /.well-known/acme-challenge { - root {{ letsencrypt_webroot_path }}; - } - {% endif %} - {% else %} - root /var/www/html; - - index index.html index.htm index.nginx-debian.html; - - location / { - # First attempt to serve request as file, then - # as directory, then fall back to displaying a 404. - try_files $uri $uri/ =404; - } - {% endif %} - - location /status { - # Turn on nginx stats - stub_status on; - access_log off; - {% for host in nginx_status_allow %} - allow {{ host }}; - {% endfor %} - deny all; - } -} diff --git a/ansible/roles/python36/defaults/main.yml b/ansible/roles/python36/defaults/main.yml deleted file mode 100644 index 4f57099..0000000 --- a/ansible/roles/python36/defaults/main.yml +++ /dev/null @@ -1,10 +0,0 @@ ---- -python36_version: 3.6.3 -python36_packages: - - "libpython3.6-minimal_{{python36_version}}-1.deb9u1_amd64" - - "libpython3.6-stdlib_{{python36_version}}-1.deb9u1_amd64" - - "libpython3.6_{{python36_version}}-1.deb9u1_amd64" - - "python3.6-minimal_{{python36_version}}-1.deb9u1_amd64" - - "python3.6_{{python36_version}}-1.deb9u1_amd64" - - "libpython3.6-dev_{{python36_version}}-1.deb9u1_amd64" - - "python3.6-dev_{{python36_version}}-1.deb9u1_amd64" diff --git a/ansible/roles/python36/tasks/main.yml b/ansible/roles/python36/tasks/main.yml deleted file mode 100644 index fbf7f7b..0000000 --- a/ansible/roles/python36/tasks/main.yml +++ /dev/null @@ -1,46 +0,0 @@ ---- -- name: Ensure python essential dependencies are installed - apt: pkg={{item}} state=present - with_items: - - build-essential - - pkg-config - - libexpat1-dev - - libssl1.1 - - mime-support - - ca-certificates - - libmpdec2 - - libreadline7 - - libsqlite3-0 - tags: - - system - -- name: Create tmp packages download directory - file: path=/tmp/python36-packages state=directory - -# TODO install and download only if needed (not already installed) -- name: Download python3.6 packages - get_url: - url: 'https://github.com/chriskuehl/python3.6-debian-stretch/releases/download/v{{python36_version}}-1-deb9u1/{{item}}.deb' - dest: '/tmp/python36-packages/{{item}}.deb' - with_items: '{{ python36_packages }}' - tags: - - python - - system - -- name: Install python3.6 packages - apt: deb="/tmp/python36-packages/{{item}}.deb" state=present - with_items: '{{ python36_packages }}' - tags: - - python - - system - -- name: Download get-pip - get_url: - url: https://bootstrap.pypa.io/get-pip.py - dest: /tmp/python36-packages/get-pip.py - -- name: Install pip - command: python3.6 /tmp/python36-packages/get-pip.py - -- name: Install virtualenv - command: pip3 install -U virtualenv diff --git a/csvapi/consumer.py b/csvapi/consumer.py index 62d42ef..803f1e3 100644 --- a/csvapi/consumer.py +++ b/csvapi/consumer.py @@ -1,10 +1,18 @@ -import logging import asyncio +import json +import os + +from datetime import datetime + +import boto3 +import pandas as pd + +from botocore.client import Config, ClientError + from csvapi.parseview import ParseView from csvapi.profileview import ProfileView from csvapi.setup_logger import logger -import requests -import os +from csvapi.utils import get_hash, create_connection from config import DB_ROOT_DIR @@ -12,178 +20,173 @@ MINIO_USER = os.environ.get("MINIO_USER", "minio") MINIO_PASSWORD = os.environ.get("MINIO_PASSWORD", "password") -from csvapi.utils import get_hash, create_connection - -import boto3 -from botocore.client import Config, ClientError -import json -from datetime import datetime -import pandas as pd def run_process_message(key: str, data: dict, topic: str) -> None: asyncio.get_event_loop().run_until_complete(process_message(key, data, topic)) + async def process_message(key: str, message: dict, topic: str) -> None: # Get url # Should think if we keep that - #r = requests.get('https://www.data.gouv.fr/api/1/datasets/{}/resources/{}'.format(message['meta']['dataset_id'], key)) - #url = r.json()['url'] + # r = requests.get('https://www.data.gouv.fr/api/1/datasets/{}/resources/{}'.format(message['meta']['dataset_id'], key)) # noqa + # url = r.json()['url'] if message is not None and message['service'] == 'csvdetective': - #try: - url = 'https://www.data.gouv.fr/fr/datasets/r/{}'.format(key) - urlhash = get_hash(url) - logger.info(urlhash) - - # Connect to minio - s3_client = boto3.client( - "s3", - endpoint_url=MINIO_URL, - aws_access_key_id=MINIO_USER, - aws_secret_access_key=MINIO_PASSWORD, - config=Config(signature_version="s3v4"), - ) - - try: - s3_client.head_bucket(Bucket=message['value']['data_location']['bucket']) - except ClientError as e: - logger.error(e) - logger.error( - "Bucket {} does not exist or credentials are invalid".format( - message['value']['location']['bucket'] - ) + url = 'https://www.data.gouv.fr/fr/datasets/r/{}'.format(key) + urlhash = get_hash(url) + logger.info(urlhash) + + # Connect to minio + s3_client = boto3.client( + "s3", + endpoint_url=MINIO_URL, + aws_access_key_id=MINIO_USER, + aws_secret_access_key=MINIO_PASSWORD, + config=Config(signature_version="s3v4"), + ) + + try: + s3_client.head_bucket(Bucket=message['value']['data_location']['bucket']) + except ClientError as e: + logger.error(e) + logger.error( + "Bucket {} does not exist or credentials are invalid".format( + message['value']['location']['bucket'] ) - return - - # Load csv-detective report - response = s3_client.get_object(Bucket = message['value']['report_location']['bucket'], Key = message['value']['report_location']['key']) - content = response['Body'] - csv_detective_report = json.loads(content.read()) - - # Parse file and store it to sqlite - parseViewInstance = ParseView() - await parseViewInstance.parse_from_consumer( - parseViewInstance, - url=url, - urlhash=urlhash, - csv_detective_report = csv_detective_report ) - - # Profile file - profileViewInstance = ProfileView() - profile_report = await profileViewInstance.get_minimal_profile( - profileViewInstance, - urlhash=urlhash, - csv_detective_report = csv_detective_report - ) - - # Save to sql - conn = create_connection(DB_ROOT_DIR+'/'+urlhash+'.db') - #c = conn.cursor() - - general_infos = [ - { - 'encoding': csv_detective_report['encoding'], - 'separator': csv_detective_report['separator'], - 'header_row_idx': csv_detective_report['header_row_idx'], - 'total_lines': profile_report['table']['n'], - 'nb_columns': profile_report['table']['n_var'], - 'nb_cells_missing': profile_report['table']['n_cells_missing'], - 'nb_vars_with_missing': profile_report['table']['n_vars_with_missing'], - 'nb_vars_all_missing': profile_report['table']['n_vars_all_missing'], - 'date_last_check': datetime.today().strftime('%Y-%m-%d'), - 'dataset_id': message['meta']['dataset_id'], - 'resource_id': key - } + return + + # Load csv-detective report + response = s3_client.get_object( + Bucket=message['value']['report_location']['bucket'], + Key=message['value']['report_location']['key'] + ) + content = response['Body'] + csv_detective_report = json.loads(content.read()) + + # Parse file and store it to sqlite + parseViewInstance = ParseView() + await parseViewInstance.parse_from_consumer( + parseViewInstance, + url=url, + urlhash=urlhash, + csv_detective_report=csv_detective_report, + ) + + # Profile file + profileViewInstance = ProfileView() + profile_report = await profileViewInstance.get_minimal_profile( + profileViewInstance, + urlhash=urlhash, + csv_detective_report=csv_detective_report, + ) + + # Save to sql + conn = create_connection(DB_ROOT_DIR+'/'+urlhash+'.db') + # c = conn.cursor() + + general_infos = [ + { + 'encoding': csv_detective_report['encoding'], + 'separator': csv_detective_report['separator'], + 'header_row_idx': csv_detective_report['header_row_idx'], + 'total_lines': profile_report['table']['n'], + 'nb_columns': profile_report['table']['n_var'], + 'nb_cells_missing': profile_report['table']['n_cells_missing'], + 'nb_vars_with_missing': profile_report['table']['n_vars_with_missing'], + 'nb_vars_all_missing': profile_report['table']['n_vars_all_missing'], + 'date_last_check': datetime.today().strftime('%Y-%m-%d'), + 'dataset_id': message['meta']['dataset_id'], + 'resource_id': key + } + ] + df = pd.DataFrame(general_infos) + df.to_sql('general_infos', con=conn, if_exists='replace', index=False) + + columns_infos = [] + categorical_infos = [] + top_infos = [] + numeric_infos = [] + numeric_plot_infos = [] + for col in profile_report['variables']: + column_info = {} + column_info['name'] = col + column_info['format'] = csv_detective_report['columns'][col]['format'] + column_info['nb_distinct'] = profile_report['variables'][col]['n_distinct'] + column_info['is_unique'] = profile_report['variables'][col]['is_unique'] + column_info['nb_unique'] = profile_report['variables'][col]['n_unique'] + column_info['type'] = profile_report['variables'][col]['type'] + column_info['nb_missing'] = profile_report['variables'][col]['n_missing'] + column_info['count'] = profile_report['variables'][col]['count'] + columns_infos.append(column_info) + + cat_cols = [ + 'siren', 'siret', 'code_postal', 'code_commune_insee', + 'code_departement', 'code_region', 'tel_fr', ] - df = pd.DataFrame(general_infos) - df.to_sql('general_infos',con=conn, if_exists='replace', index=False) - - columns_infos = [] - categorical_infos = [] - top_infos = [] - numeric_infos = [] - numeric_plot_infos = [] - for col in profile_report['variables']: - column_info = {} - column_info['name'] = col - column_info['format'] = csv_detective_report['columns'][col]['format'] - column_info['nb_distinct'] = profile_report['variables'][col]['n_distinct'] - column_info['is_unique'] = profile_report['variables'][col]['is_unique'] - column_info['nb_unique'] = profile_report['variables'][col]['n_unique'] - column_info['type'] = profile_report['variables'][col]['type'] - column_info['nb_missing'] = profile_report['variables'][col]['n_missing'] - column_info['count'] = profile_report['variables'][col]['count'] - columns_infos.append(column_info) - - if(csv_detective_report['columns'][col]['format'] in ['siren', 'siret', 'code_postal', 'code_commune_insee', 'code_departement', 'code_region', 'tel_fr']): - column_info['type'] = 'Categorical' - - if((column_info['type'] == 'Categorical') & (len(profile_report['variables'][col]['value_counts_without_nan']) < 10)): - for cat in profile_report['variables'][col]['value_counts_without_nan']: - categorical_info = {} - categorical_info['column'] = col - categorical_info['value'] = cat - categorical_info['nb'] = profile_report['variables'][col]['value_counts_without_nan'][cat] - categorical_infos.append(categorical_info) - - if(column_info['type'] == 'Numeric'): - numeric_info = {} - numeric_info['column'] = col - numeric_info['mean'] = profile_report['variables'][col]['mean'] - numeric_info['std'] = profile_report['variables'][col]['std'] - numeric_info['min'] = profile_report['variables'][col]['min'] - numeric_info['max'] = profile_report['variables'][col]['max'] - numeric_infos.append(numeric_info) - for i in range(len(profile_report['variables'][col]['histogram']['bin_edges'])): - numeric_plot_info = {} - numeric_plot_info['column'] = col - numeric_plot_info['value'] = profile_report['variables'][col]['histogram']['bin_edges'][i] - numeric_plot_info['type'] = 'bin_edges' - numeric_plot_infos.append(numeric_plot_info) - - for i in range(len(profile_report['variables'][col]['histogram']['counts'])): - numeric_plot_info = {} - numeric_plot_info['column'] = col - numeric_plot_info['value'] = profile_report['variables'][col]['histogram']['counts'][i] - numeric_plot_info['type'] = 'counts' - numeric_plot_infos.append(numeric_plot_info) - - - cpt = 0 - for top in profile_report['variables'][col]['value_counts_without_nan']: - if (cpt < 10): - top_info = {} - top_info['column'] = col - top_info['value'] = top - top_info['nb'] = profile_report['variables'][col]['value_counts_without_nan'][top] - top_infos.append(top_info) - cpt = cpt + 1 - - df = pd.DataFrame(columns_infos) - if(df.shape[0] > 0): - df.to_sql('columns_infos',con=conn, if_exists='replace', index=False) - - df = pd.DataFrame(categorical_infos) - if(df.shape[0] > 0): - df.to_sql('categorical_infos',con=conn, if_exists='replace', index=False) - - df = pd.DataFrame(top_infos) - if(df.shape[0] > 0): - df.to_sql('top_infos',con=conn, if_exists='replace', index=False) - - df = pd.DataFrame(numeric_infos) - if(df.shape[0] > 0): - df.to_sql('numeric_infos',con=conn, if_exists='replace', index=False) - - df = pd.DataFrame(numeric_plot_infos) - if(df.shape[0] > 0): - df.to_sql('numeric_plot_infos',con=conn, if_exists='replace', index=False) - - conn.commit() - - print('ok') - - # Consolider detection de type pandas profiling - # on dirait qu'il ne comprend pas le dtype à la lecture (notamment sur siren) - #except: - # logger.info('Error with message', message) + if csv_detective_report['columns'][col]['format'] in cat_cols: + column_info['type'] = 'Categorical' + + if column_info['type'] == 'Categorical' and \ + len(profile_report['variables'][col]['value_counts_without_nan']) < 10: + for cat in profile_report['variables'][col]['value_counts_without_nan']: + categorical_info = {} + categorical_info['column'] = col + categorical_info['value'] = cat + categorical_info['nb'] = profile_report['variables'][col]['value_counts_without_nan'][cat] + categorical_infos.append(categorical_info) + + if column_info['type'] == 'Numeric': + numeric_info = {} + numeric_info['column'] = col + numeric_info['mean'] = profile_report['variables'][col]['mean'] + numeric_info['std'] = profile_report['variables'][col]['std'] + numeric_info['min'] = profile_report['variables'][col]['min'] + numeric_info['max'] = profile_report['variables'][col]['max'] + numeric_infos.append(numeric_info) + for i in range(len(profile_report['variables'][col]['histogram']['bin_edges'])): + numeric_plot_info = {} + numeric_plot_info['column'] = col + numeric_plot_info['value'] = profile_report['variables'][col]['histogram']['bin_edges'][i] + numeric_plot_info['type'] = 'bin_edges' + numeric_plot_infos.append(numeric_plot_info) + + for i in range(len(profile_report['variables'][col]['histogram']['counts'])): + numeric_plot_info = {} + numeric_plot_info['column'] = col + numeric_plot_info['value'] = profile_report['variables'][col]['histogram']['counts'][i] + numeric_plot_info['type'] = 'counts' + numeric_plot_infos.append(numeric_plot_info) + + cpt = 0 + for top in profile_report['variables'][col]['value_counts_without_nan']: + if (cpt < 10): + top_info = {} + top_info['column'] = col + top_info['value'] = top + top_info['nb'] = profile_report['variables'][col]['value_counts_without_nan'][top] + top_infos.append(top_info) + cpt = cpt + 1 + + df = pd.DataFrame(columns_infos) + if df.shape[0] > 0: + df.to_sql('columns_infos', con=conn, if_exists='replace', index=False) + + df = pd.DataFrame(categorical_infos) + if df.shape[0] > 0: + df.to_sql('categorical_infos', con=conn, if_exists='replace', index=False) + + df = pd.DataFrame(top_infos) + if df.shape[0] > 0: + df.to_sql('top_infos', con=conn, if_exists='replace', index=False) + + df = pd.DataFrame(numeric_infos) + if df.shape[0] > 0: + df.to_sql('numeric_infos', con=conn, if_exists='replace', index=False) + + df = pd.DataFrame(numeric_plot_infos) + if df.shape[0] > 0: + df.to_sql('numeric_plot_infos', con=conn, if_exists='replace', index=False) + + conn.commit() + + print('ok') diff --git a/csvapi/parser.py b/csvapi/parser.py index 51e16da..e6d0312 100644 --- a/csvapi/parser.py +++ b/csvapi/parser.py @@ -11,7 +11,7 @@ SNIFF_LIMIT = 4096 -CSV_FILETYPES = ('text/plain', 'application/csv') +CSV_FILETYPES = ('text/plain', 'application/csv', 'text/csv') def detect_type(filepath): @@ -56,9 +56,8 @@ def from_csv(filepath, encoding='utf-8', sniff_limit=SNIFF_LIMIT, agate_types=No 'column_types': agate_tester() } return agate.Table.from_csv(filepath, **kwargs) - except: - logger.error('error casting') - + except Exception as e: + logger.error('error casting %s', e) def from_excel(filepath, xlsx=False): @@ -86,4 +85,3 @@ def parse(filepath, urlhash, storage, encoding=None, sniff_limit=SNIFF_LIMIT, ag else: raise Exception(f'Unsupported file type {file_type}') return to_sql(table, urlhash, storage) - diff --git a/csvapi/parseview.py b/csvapi/parseview.py index 50375ea..784addc 100644 --- a/csvapi/parseview.py +++ b/csvapi/parseview.py @@ -2,7 +2,6 @@ import tempfile import aiohttp -from sqlalchemy import column import validators from quart import request, jsonify, current_app as app @@ -14,7 +13,6 @@ from csvapi.setup_logger import logger from csvapi.type_tester import convert_types -import os from config import DB_ROOT_DIR, CSV_SNIFF_LIMIT, MAX_FILE_SIZE @@ -28,7 +26,8 @@ async def parse_from_consumer(self, url: str, urlhash: str, csv_detective_report column_types.append(csv_detective_report['columns'][col]['python_type']) agate_types = convert_types(column_types) - await self.do_parse(url=url, + await self.do_parse( + url=url, urlhash=urlhash, encoding=csv_detective_report['encoding'], storage=storage, @@ -38,9 +37,8 @@ async def parse_from_consumer(self, url: str, urlhash: str, csv_detective_report agate_types=agate_types ) - @staticmethod - async def do_parse(url, urlhash, encoding, storage, logger, sniff_limit, max_file_size, agate_types = None): + async def do_parse(url, urlhash, encoding, storage, logger, sniff_limit, max_file_size, agate_types=None): logger.debug('* do_parse %s (%s)', urlhash, url) tmp = tempfile.NamedTemporaryFile(delete=False) chunk_count = 0 diff --git a/csvapi/profileview.py b/csvapi/profileview.py index f6a60f6..71d4d17 100644 --- a/csvapi/profileview.py +++ b/csvapi/profileview.py @@ -17,15 +17,18 @@ import json from csvapi.setup_logger import logger + + class ProfileView(MethodView): def get_dataframe(self, db_info, dtype=None): dsn = 'file:{}?immutable=1'.format(db_info['db_path']) - conn = sqlite3.connect(dsn) + conn = sqlite3.connect(dsn, uri=True) sql = 'SELECT * FROM [{}]'.format(db_info['table_name']) try: df = pd.read_sql_query(sql, con=conn, dtype=dtype) - except: + # TODO: check if correct exception type + except ValueError: df = pd.read_sql_query(sql, con=conn) logger.info('problem with python types') return df @@ -33,7 +36,7 @@ def get_dataframe(self, db_info, dtype=None): def make_profile(self, db_info): df = self.get_dataframe(db_info) - if(app.config['PANDAS_PROFILING_CONFIG_MIN']): + if app.config['PANDAS_PROFILING_CONFIG_MIN']: profile = ProfileReport(df, config_file="profiling-minimal.yml") else: profile = ProfileReport(df) @@ -56,7 +59,6 @@ async def get(self, urlhash): return await send_from_directory(path.parent, path.name) - async def get_minimal_profile(self, url: str, urlhash: str, csv_detective_report: dict) -> None: db_info = get_db_info(urlhash) p = Path(db_info['db_path']) @@ -70,7 +72,11 @@ async def get_minimal_profile(self, url: str, urlhash: str, csv_detective_report python_types = convert_python_types(csv_detective_report['columns']) df = self.get_dataframe(db_info, dtype=python_types) - profile = ProfileReport(df, minimal=True, vars=dict(num={"low_categorical_threshold": 0}), plot=dict(histogram={"bins": 10})) + profile = ProfileReport( + df, minimal=True, + vars=dict(num={"low_categorical_threshold": 0}), + plot=dict(histogram={"bins": 10}) + ) profile_report = json.loads(profile.to_json()) return profile_report except (sqlite3.OperationalError, sqlite3.IntegrityError) as e: diff --git a/csvapi/tableview.py b/csvapi/tableview.py index 8d62775..a3b8a76 100644 --- a/csvapi/tableview.py +++ b/csvapi/tableview.py @@ -47,7 +47,9 @@ class TableView(MethodView): async def execute(self, sql, db_info, params=None): """Executes sql against db_name in a thread""" dsn = 'file:{}?immutable=1'.format(db_info['db_path']) - async with aiosqlite.connect(dsn) as conn: + # specify uri=True to make sure `file:xxx` is supported, + # however the backend sqlite is configured (eg default MacOS) + async with aiosqlite.connect(dsn, uri=True) as conn: conn.text_factory = lambda x: str(x, 'utf-8', 'replace') # this will raise # {"details": "interrupted", @@ -179,7 +181,7 @@ async def general_infos(self, db_info): rows, description = await self.execute( sql, db_info, params=params ) - if(rows[0][0] != 0): + if rows[0][0] != 0: sql = 'SELECT * FROM general_infos' rows, description = await self.execute( sql, db_info, params=params @@ -190,11 +192,10 @@ async def general_infos(self, db_info): for col in columns: res[col] = rows[0][cpt] cpt = cpt + 1 - + return res else: return {} - async def columns_infos(self, db_info): params = {} @@ -202,7 +203,7 @@ async def columns_infos(self, db_info): rows, description = await self.execute( sql, db_info, params=params ) - if(rows[0][0] != 0): + if rows[0][0] != 0: sql = 'SELECT * FROM columns_infos' rows, description = await self.execute( sql, db_info, params=params @@ -216,7 +217,7 @@ async def columns_infos(self, db_info): for col in columns[1:]: res[row[0]][col] = row[cpt] cpt = cpt + 1 - + res = await self.top_and_categorical_infos(db_info, res, 'top_infos') res = await self.top_and_categorical_infos(db_info, res, 'categorical_infos') res = await self.numeric_infos(db_info, res) @@ -231,14 +232,14 @@ async def top_and_categorical_infos(self, db_info, res, table_name): rows, description = await self.execute( sql, db_info, params=params ) - if(rows[0][0] != 0): + if rows[0][0] != 0: sql = 'SELECT * FROM {}'.format(table_name) rows, description = await self.execute( sql, db_info, params=params ) - + for row in rows: - if(table_name not in res[row[0]]): + if table_name not in res[row[0]]: res[row[0]][table_name] = [] inter = {} inter['value'] = row[1] @@ -257,16 +258,16 @@ async def numeric_infos(self, db_info, res): rows, description = await self.execute( sql, db_info, params=params ) - if(rows[0][0] != 0): + if rows[0][0] != 0: sql = 'SELECT * FROM {}'.format('numeric_infos') rows, description = await self.execute( sql, db_info, params=params ) - + for row in rows: - if('numeric_infos' not in res[row[0]]): + if 'numeric_infos' not in res[row[0]]: res[row[0]]['numeric_infos'] = {} - + res[row[0]]['numeric_infos']['mean'] = row[1] res[row[0]]['numeric_infos']['std'] = row[2] res[row[0]]['numeric_infos']['min'] = row[3] @@ -284,22 +285,22 @@ async def numeric_plot_infos(self, db_info, res): rows, description = await self.execute( sql, db_info, params=params ) - if(rows[0][0] != 0): + if rows[0][0] != 0: sql = 'SELECT * FROM {}'.format('numeric_plot_infos') rows, description = await self.execute( sql, db_info, params=params ) - + for row in rows: - if('numeric_plot_infos' not in res[row[0]]): + if 'numeric_plot_infos' not in res[row[0]]: res[row[0]]['numeric_plot_infos'] = {} - if('counts' not in res[row[0]]['numeric_plot_infos']): + if 'counts' not in res[row[0]]['numeric_plot_infos']: res[row[0]]['numeric_plot_infos']['counts'] = [] - if('bin_edges' not in res[row[0]]['numeric_plot_infos']): + if 'bin_edges' not in res[row[0]]['numeric_plot_infos']: res[row[0]]['numeric_plot_infos']['bin_edges'] = [] - if(row[2] == 'counts'): + if row[2] == 'counts': res[row[0]]['numeric_plot_infos']['counts'].append(row[1]) - if(row[2] == 'bin_edges'): + if row[2] == 'bin_edges': res[row[0]]['numeric_plot_infos']['bin_edges'].append(row[1]) return res @@ -307,4 +308,3 @@ async def numeric_plot_infos(self, db_info, res): for col in res: res[col]['numeric_plot_infos'] = {} return res - diff --git a/csvapi/type_tester.py b/csvapi/type_tester.py index f4d863b..ad2a5b6 100644 --- a/csvapi/type_tester.py +++ b/csvapi/type_tester.py @@ -17,7 +17,6 @@ from stdnum.fr.siren import is_valid as is_valid_siren from stdnum.fr.siret import is_valid as is_valid_siret -from csvapi.setup_logger import logger class Time(DataType): # Detect an hour minute string. @@ -74,6 +73,7 @@ def agate_tester(): ] ) + def convert_types(column_types): convert_dict = { 'string': Text(), @@ -81,7 +81,7 @@ def convert_types(column_types): 'float': Number(), 'number': Number(), 'date': Date(), - #'bool': Boolean(), + # 'bool': Boolean(), 'bool': Text(), 'geopoint': Text(), 'geojson': Text(), @@ -93,9 +93,10 @@ def convert_types(column_types): agate_types = [] for ct in column_types: agate_types.append(convert_dict[ct]) - + return agate_types + def convert_python_types(columns): convert_dict = { 'string': str, @@ -103,7 +104,7 @@ def convert_python_types(columns): 'float': float, 'number': float, 'date': str, - #'bool': bool, + # 'bool': bool, 'bool': str, 'geopoint': str, 'geojson': str, @@ -115,5 +116,5 @@ def convert_python_types(columns): python_types = {} for col in columns: python_types[col] = convert_dict[columns[col]['python_type']] - - return python_types \ No newline at end of file + + return python_types diff --git a/csvapi/utils.py b/csvapi/utils.py index 548adb5..6ba06e5 100644 --- a/csvapi/utils.py +++ b/csvapi/utils.py @@ -12,7 +12,7 @@ def get_db_info(urlhash, storage=None): - if(app): + if app: # app.config not thread safe, sometimes we need to pass storage directly db_storage = storage or app.config['DB_ROOT_DIR'] profile_storage = app.config['PROFILES_ROOT_DIR'] @@ -47,6 +47,4 @@ def already_exists(urlhash): def create_connection(db_file): - conn = None - conn = sqlite3.connect(db_file) - return conn + return sqlite3.connect(db_file, uri=True) diff --git a/csvapi/webservice.py b/csvapi/webservice.py index e986eaa..7f10811 100644 --- a/csvapi/webservice.py +++ b/csvapi/webservice.py @@ -57,8 +57,8 @@ def handle_not_found(error): @app.errorhandler(APIError) def handle_api_error(error): error_id = handle_and_print_error(error) - app.logger.error(error.message) data = error.to_dict() + app.logger.error(f"{data.get('error')}: {data.get('details', '')}") data['error_id'] = error_id response = jsonify(data) response.status_code = error.status diff --git a/poetry.lock b/poetry.lock new file mode 100644 index 0000000..721ec27 --- /dev/null +++ b/poetry.lock @@ -0,0 +1,2344 @@ +[[package]] +name = "agate" +version = "1.6.3" +description = "A data analysis library that is optimized for humans instead of machines." +category = "main" +optional = false +python-versions = "*" + +[package.dependencies] +Babel = ">=2.0" +isodate = ">=0.5.4" +leather = ">=0.3.2" +parsedatetime = ">=2.1,<2.5 || >2.5,<2.6 || >2.6" +python-slugify = ">=1.2.1" +pytimeparse = ">=1.1.5" +six = ">=1.9.0" + +[package.extras] +docs = ["Sphinx (>=1.2.2)", "sphinx-rtd-theme (>=0.1.6)"] +test = ["PyICU (>=2.4.2)", "coverage (>=3.7.1)", "cssselect (>=0.9.1)", "lxml (>=3.6.0)", "mock (>=1.3.0)", "nose (>=1.1.2)", "pytz (>=2015.4)", "unittest2 (>=1.1.0)"] + +[[package]] +name = "agate-excel" +version = "0.2.5" +description = "agate-excel adds read support for Excel files (xls and xlsx) to agate." +category = "main" +optional = false +python-versions = "*" + +[package.dependencies] +agate = ">=1.5.0" +olefile = "*" +openpyxl = ">=2.3.0" +six = "*" +xlrd = ">=0.9.4" + +[package.extras] +docs = ["Sphinx (>=1.2.2)", "sphinx-rtd-theme (>=0.1.6)"] +test = ["nose (>=1.1.2)"] + +[[package]] +name = "agate-sql" +version = "0.5.8" +description = "agate-sql adds SQL read/write support to agate." +category = "main" +optional = false +python-versions = "*" + +[package.dependencies] +agate = ">=1.5.0" +sqlalchemy = ">=1.0.8" + +[package.extras] +docs = ["Sphinx (>=1.2.2)", "sphinx-rtd-theme (>=0.1.6)"] +test = ["crate", "geojson", "nose (>=1.1.2)"] + +[[package]] +name = "aiofiles" +version = "0.8.0" +description = "File support for asyncio." +category = "main" +optional = false +python-versions = ">=3.6,<4.0" + +[[package]] +name = "aiohttp" +version = "3.7.4.post0" +description = "Async http client/server framework (asyncio)" +category = "main" +optional = false +python-versions = ">=3.6" + +[package.dependencies] +async-timeout = ">=3.0,<4.0" +attrs = ">=17.3.0" +chardet = ">=2.0,<5.0" +multidict = ">=4.5,<7.0" +typing-extensions = ">=3.6.5" +yarl = ">=1.0,<2.0" + +[package.extras] +speedups = ["aiodns", "brotlipy", "cchardet"] + +[[package]] +name = "aioresponses" +version = "0.7.3" +description = "Mock out requests made by ClientSession from aiohttp package" +category = "dev" +optional = false +python-versions = "*" + +[package.dependencies] +aiohttp = ">=2.0.0,<4.0.0" + +[[package]] +name = "aiosqlite" +version = "0.16.1" +description = "asyncio bridge to the standard sqlite3 module" +category = "main" +optional = false +python-versions = ">=3.6" + +[package.dependencies] +typing_extensions = ">=3.7.2" + +[[package]] +name = "async-timeout" +version = "3.0.1" +description = "Timeout context manager for asyncio programs" +category = "main" +optional = false +python-versions = ">=3.5.3" + +[[package]] +name = "atomicwrites" +version = "1.4.1" +description = "Atomic file writes." +category = "dev" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" + +[[package]] +name = "attrs" +version = "22.1.0" +description = "Classes Without Boilerplate" +category = "main" +optional = false +python-versions = ">=3.5" + +[package.extras] +dev = ["cloudpickle", "coverage[toml] (>=5.0.2)", "furo", "hypothesis", "mypy (>=0.900,!=0.940)", "pre-commit", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "sphinx", "sphinx-notfound-page", "zope.interface"] +docs = ["furo", "sphinx", "sphinx-notfound-page", "zope.interface"] +tests = ["cloudpickle", "coverage[toml] (>=5.0.2)", "hypothesis", "mypy (>=0.900,!=0.940)", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "zope.interface"] +tests_no_zope = ["cloudpickle", "coverage[toml] (>=5.0.2)", "hypothesis", "mypy (>=0.900,!=0.940)", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins"] + +[[package]] +name = "babel" +version = "2.10.3" +description = "Internationalization utilities" +category = "main" +optional = false +python-versions = ">=3.6" + +[package.dependencies] +pytz = ">=2015.7" + +[[package]] +name = "blinker" +version = "1.5" +description = "Fast, simple object-to-object and broadcast signaling" +category = "main" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" + +[[package]] +name = "boto3" +version = "1.21.46" +description = "The AWS SDK for Python" +category = "main" +optional = false +python-versions = ">= 3.6" + +[package.dependencies] +botocore = ">=1.24.46,<1.25.0" +jmespath = ">=0.7.1,<2.0.0" +s3transfer = ">=0.5.0,<0.6.0" + +[package.extras] +crt = ["botocore[crt] (>=1.21.0,<2.0a0)"] + +[[package]] +name = "botocore" +version = "1.24.46" +description = "Low-level, data-driven core of boto 3." +category = "main" +optional = false +python-versions = ">= 3.6" + +[package.dependencies] +jmespath = ">=0.7.1,<2.0.0" +python-dateutil = ">=2.1,<3.0.0" +urllib3 = ">=1.25.4,<1.27" + +[package.extras] +crt = ["awscrt (==0.13.8)"] + +[[package]] +name = "cchardet" +version = "2.1.7" +description = "cChardet is high speed universal character encoding detector." +category = "main" +optional = false +python-versions = "*" + +[[package]] +name = "certifi" +version = "2022.6.15" +description = "Python package for providing Mozilla's CA Bundle." +category = "main" +optional = false +python-versions = ">=3.6" + +[[package]] +name = "chardet" +version = "4.0.0" +description = "Universal encoding detector for Python 2 and 3" +category = "main" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" + +[[package]] +name = "charset-normalizer" +version = "2.0.12" +description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." +category = "main" +optional = false +python-versions = ">=3.5.0" + +[package.extras] +unicode_backport = ["unicodedata2"] + +[[package]] +name = "click" +version = "8.1.3" +description = "Composable command line interface toolkit" +category = "main" +optional = false +python-versions = ">=3.7" + +[package.dependencies] +colorama = {version = "*", markers = "platform_system == \"Windows\""} + +[[package]] +name = "click-default-group" +version = "1.2.2" +description = "Extends click.Group to invoke a command without explicit subcommand name" +category = "main" +optional = false +python-versions = "*" + +[package.dependencies] +click = "*" + +[[package]] +name = "colorama" +version = "0.4.5" +description = "Cross-platform colored terminal text." +category = "main" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" + +[[package]] +name = "coverage" +version = "6.4.4" +description = "Code coverage measurement for Python" +category = "dev" +optional = false +python-versions = ">=3.7" + +[package.extras] +toml = ["tomli"] + +[[package]] +name = "cycler" +version = "0.11.0" +description = "Composable style cycles" +category = "main" +optional = false +python-versions = ">=3.6" + +[[package]] +name = "decorator" +version = "5.1.1" +description = "Decorators for Humans" +category = "main" +optional = false +python-versions = ">=3.5" + +[[package]] +name = "et-xmlfile" +version = "1.1.0" +description = "An implementation of lxml.xmlfile for the standard library" +category = "main" +optional = false +python-versions = ">=3.6" + +[[package]] +name = "flake8" +version = "5.0.4" +description = "the modular source code checker: pep8 pyflakes and co" +category = "dev" +optional = false +python-versions = ">=3.6.1" + +[package.dependencies] +mccabe = ">=0.7.0,<0.8.0" +pycodestyle = ">=2.9.0,<2.10.0" +pyflakes = ">=2.5.0,<2.6.0" + +[[package]] +name = "fonttools" +version = "4.37.1" +description = "Tools to manipulate font files" +category = "main" +optional = false +python-versions = ">=3.7" + +[package.extras] +all = ["brotli (>=1.0.1)", "brotlicffi (>=0.8.0)", "fs (>=2.2.0,<3)", "lxml (>=4.0,<5)", "lz4 (>=1.7.4.2)", "matplotlib", "munkres", "scipy", "skia-pathops (>=0.5.0)", "sympy", "uharfbuzz (>=0.23.0)", "unicodedata2 (>=14.0.0)", "xattr", "zopfli (>=0.1.4)"] +graphite = ["lz4 (>=1.7.4.2)"] +interpolatable = ["munkres", "scipy"] +lxml = ["lxml (>=4.0,<5)"] +pathops = ["skia-pathops (>=0.5.0)"] +plot = ["matplotlib"] +repacker = ["uharfbuzz (>=0.23.0)"] +symfont = ["sympy"] +type1 = ["xattr"] +ufo = ["fs (>=2.2.0,<3)"] +unicode = ["unicodedata2 (>=14.0.0)"] +woff = ["brotli (>=1.0.1)", "brotlicffi (>=0.8.0)", "zopfli (>=0.1.4)"] + +[[package]] +name = "future" +version = "0.18.2" +description = "Clean single-source support for Python 3 and 2" +category = "main" +optional = false +python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" + +[[package]] +name = "greenlet" +version = "1.1.3" +description = "Lightweight in-process concurrent programming" +category = "main" +optional = false +python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*" + +[package.extras] +docs = ["sphinx"] + +[[package]] +name = "h11" +version = "0.13.0" +description = "A pure-Python, bring-your-own-I/O implementation of HTTP/1.1" +category = "main" +optional = false +python-versions = ">=3.6" + +[[package]] +name = "h2" +version = "4.1.0" +description = "HTTP/2 State-Machine based protocol implementation" +category = "main" +optional = false +python-versions = ">=3.6.1" + +[package.dependencies] +hpack = ">=4.0,<5" +hyperframe = ">=6.0,<7" + +[[package]] +name = "hpack" +version = "4.0.0" +description = "Pure-Python HPACK header compression" +category = "main" +optional = false +python-versions = ">=3.6.1" + +[[package]] +name = "htmlmin" +version = "0.1.12" +description = "An HTML Minifier" +category = "main" +optional = false +python-versions = "*" + +[[package]] +name = "hypercorn" +version = "0.13.2" +description = "A ASGI Server based on Hyper libraries and inspired by Gunicorn" +category = "main" +optional = false +python-versions = ">=3.7" + +[package.dependencies] +h11 = "*" +h2 = ">=3.1.0" +priority = "*" +toml = "*" +wsproto = ">=0.14.0" + +[package.extras] +h3 = ["aioquic (>=0.9.0,<1.0)"] +trio = ["trio (>=0.11.0)"] +uvloop = ["uvloop"] + +[[package]] +name = "hyperframe" +version = "6.0.1" +description = "HTTP/2 framing layer for Python" +category = "main" +optional = false +python-versions = ">=3.6.1" + +[[package]] +name = "idna" +version = "3.3" +description = "Internationalized Domain Names in Applications (IDNA)" +category = "main" +optional = false +python-versions = ">=3.5" + +[[package]] +name = "imagehash" +version = "4.2.1" +description = "Image Hashing library" +category = "main" +optional = false +python-versions = "*" + +[package.dependencies] +numpy = "*" +pillow = "*" +PyWavelets = "*" +scipy = "*" +six = "*" + +[[package]] +name = "iniconfig" +version = "1.1.1" +description = "iniconfig: brain-dead simple config-ini parsing" +category = "dev" +optional = false +python-versions = "*" + +[[package]] +name = "isodate" +version = "0.6.1" +description = "An ISO 8601 date/time/duration parser and formatter" +category = "main" +optional = false +python-versions = "*" + +[package.dependencies] +six = "*" + +[[package]] +name = "itsdangerous" +version = "2.1.2" +description = "Safely pass data to untrusted environments and back." +category = "main" +optional = false +python-versions = ">=3.7" + +[[package]] +name = "jinja2" +version = "3.0.3" +description = "A very fast and expressive template engine." +category = "main" +optional = false +python-versions = ">=3.6" + +[package.dependencies] +MarkupSafe = ">=2.0" + +[package.extras] +i18n = ["Babel (>=2.7)"] + +[[package]] +name = "jmespath" +version = "1.0.1" +description = "JSON Matching Expressions" +category = "main" +optional = false +python-versions = ">=3.7" + +[[package]] +name = "joblib" +version = "1.1.0" +description = "Lightweight pipelining with Python functions" +category = "main" +optional = false +python-versions = ">=3.6" + +[[package]] +name = "kafka-python" +version = "2.0.2" +description = "Pure Python client for Apache Kafka" +category = "main" +optional = false +python-versions = "*" + +[package.extras] +crc32c = ["crc32c"] + +[[package]] +name = "kiwisolver" +version = "1.4.4" +description = "A fast implementation of the Cassowary constraint solver" +category = "main" +optional = false +python-versions = ">=3.7" + +[[package]] +name = "leather" +version = "0.3.4" +description = "Python charting for 80% of humans." +category = "main" +optional = false +python-versions = "*" + +[package.dependencies] +six = ">=1.6.1" + +[[package]] +name = "markupsafe" +version = "2.1.1" +description = "Safely add untrusted strings to HTML/XML markup." +category = "main" +optional = false +python-versions = ">=3.7" + +[[package]] +name = "matplotlib" +version = "3.5.3" +description = "Python plotting package" +category = "main" +optional = false +python-versions = ">=3.7" + +[package.dependencies] +cycler = ">=0.10" +fonttools = ">=4.22.0" +kiwisolver = ">=1.0.1" +numpy = ">=1.17" +packaging = ">=20.0" +pillow = ">=6.2.0" +pyparsing = ">=2.2.1" +python-dateutil = ">=2.7" +setuptools_scm = ">=4,<7" + +[[package]] +name = "mccabe" +version = "0.7.0" +description = "McCabe checker, plugin for flake8" +category = "dev" +optional = false +python-versions = ">=3.6" + +[[package]] +name = "missingno" +version = "0.5.1" +description = "Missing data visualization module for Python." +category = "main" +optional = false +python-versions = "*" + +[package.dependencies] +matplotlib = "*" +numpy = "*" +scipy = "*" +seaborn = "*" + +[package.extras] +tests = ["pytest", "pytest-mpl"] + +[[package]] +name = "multidict" +version = "6.0.2" +description = "multidict implementation" +category = "main" +optional = false +python-versions = ">=3.7" + +[[package]] +name = "multimethod" +version = "1.8" +description = "Multiple argument dispatching." +category = "main" +optional = false +python-versions = ">=3.7" + +[[package]] +name = "networkx" +version = "2.8.6" +description = "Python package for creating and manipulating graphs and networks" +category = "main" +optional = false +python-versions = ">=3.8" + +[package.extras] +default = ["matplotlib (>=3.4)", "numpy (>=1.19)", "pandas (>=1.3)", "scipy (>=1.8)"] +developer = ["mypy (>=0.961)", "pre-commit (>=2.20)"] +doc = ["nb2plots (>=0.6)", "numpydoc (>=1.4)", "pillow (>=9.1)", "pydata-sphinx-theme (>=0.9)", "sphinx (>=5)", "sphinx-gallery (>=0.10)", "texext (>=0.6.6)"] +extra = ["lxml (>=4.6)", "pydot (>=1.4.2)", "pygraphviz (>=1.9)", "sympy (>=1.10)"] +test = ["codecov (>=2.1)", "pytest (>=7.1)", "pytest-cov (>=3.0)"] + +[[package]] +name = "numpy" +version = "1.23.2" +description = "NumPy is the fundamental package for array computing with Python." +category = "main" +optional = false +python-versions = ">=3.8" + +[[package]] +name = "olefile" +version = "0.46" +description = "Python package to parse, read and write Microsoft OLE2 files (Structured Storage or Compound Document, Microsoft Office)" +category = "main" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" + +[[package]] +name = "openpyxl" +version = "3.0.10" +description = "A Python library to read/write Excel 2010 xlsx/xlsm files" +category = "main" +optional = false +python-versions = ">=3.6" + +[package.dependencies] +et-xmlfile = "*" + +[[package]] +name = "packaging" +version = "21.3" +description = "Core utilities for Python packages" +category = "main" +optional = false +python-versions = ">=3.6" + +[package.dependencies] +pyparsing = ">=2.0.2,<3.0.5 || >3.0.5" + +[[package]] +name = "pandas" +version = "1.4.3" +description = "Powerful data structures for data analysis, time series, and statistics" +category = "main" +optional = false +python-versions = ">=3.8" + +[package.dependencies] +numpy = [ + {version = ">=1.18.5", markers = "platform_machine != \"aarch64\" and platform_machine != \"arm64\" and python_version < \"3.10\""}, + {version = ">=1.19.2", markers = "platform_machine == \"aarch64\" and python_version < \"3.10\""}, + {version = ">=1.20.0", markers = "platform_machine == \"arm64\" and python_version < \"3.10\""}, + {version = ">=1.21.0", markers = "python_version >= \"3.10\""}, +] +python-dateutil = ">=2.8.1" +pytz = ">=2020.1" + +[package.extras] +test = ["hypothesis (>=5.5.3)", "pytest (>=6.0)", "pytest-xdist (>=1.31)"] + +[[package]] +name = "pandas-profiling" +version = "3.2.0" +description = "Generate profile report for pandas DataFrame" +category = "main" +optional = false +python-versions = ">=3.6" + +[package.dependencies] +htmlmin = ">=0.1.12" +jinja2 = ">=2.11.1" +joblib = ">=1.1.0,<1.2.0" +markupsafe = ">=2.1.1,<2.2.0" +matplotlib = ">=3.2.0" +missingno = ">=0.4.2" +multimethod = ">=1.4" +numpy = ">=1.16.0" +pandas = ">=0.25.3,<1.0.0 || >1.0.0,<1.0.1 || >1.0.1,<1.0.2 || >1.0.2,<1.1.0 || >1.1.0" +phik = ">=0.11.1" +pydantic = ">=1.8.1" +PyYAML = ">=5.0.0" +requests = ">=2.24.0" +scipy = ">=1.4.1" +seaborn = ">=0.10.1" +tangled-up-in-unicode = "0.2.0" +tqdm = ">=4.48.2" +visions = {version = "0.7.4", extras = ["type_image_path"]} + +[package.extras] +notebook = ["ipywidgets (>=7.5.1)", "jupyter-client (>=5.3.4)", "jupyter-core (>=4.6.3)"] + +[[package]] +name = "parsedatetime" +version = "2.4" +description = "Parse human-readable date/time text." +category = "main" +optional = false +python-versions = "*" + +[package.dependencies] +future = "*" + +[[package]] +name = "phik" +version = "0.12.2" +description = "Phi_K correlation analyzer library" +category = "main" +optional = false +python-versions = ">=3.7" + +[package.dependencies] +joblib = ">=0.14.1" +matplotlib = ">=2.2.3" +numpy = ">=1.18.0" +pandas = ">=0.25.1" +scipy = ">=1.5.2" + +[package.extras] +test = ["jupyter-client (>=5.2.3)", "nbconvert (>=5.3.1)", "pytest (>=4.0.2)", "pytest-pylint (>=0.13.0)"] + +[[package]] +name = "pillow" +version = "9.2.0" +description = "Python Imaging Library (Fork)" +category = "main" +optional = false +python-versions = ">=3.7" + +[package.extras] +docs = ["furo", "olefile", "sphinx (>=2.4)", "sphinx-copybutton", "sphinx-issues (>=3.0.1)", "sphinx-removed-in", "sphinxext-opengraph"] +tests = ["check-manifest", "coverage", "defusedxml", "markdown2", "olefile", "packaging", "pyroma", "pytest", "pytest-cov", "pytest-timeout"] + +[[package]] +name = "pluggy" +version = "1.0.0" +description = "plugin and hook calling mechanisms for python" +category = "dev" +optional = false +python-versions = ">=3.6" + +[package.extras] +dev = ["pre-commit", "tox"] +testing = ["pytest", "pytest-benchmark"] + +[[package]] +name = "priority" +version = "2.0.0" +description = "A pure-Python implementation of the HTTP/2 priority tree" +category = "main" +optional = false +python-versions = ">=3.6.1" + +[[package]] +name = "py" +version = "1.11.0" +description = "library with cross-python path, ini-parsing, io, code, log facilities" +category = "dev" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" + +[[package]] +name = "pycodestyle" +version = "2.9.1" +description = "Python style guide checker" +category = "dev" +optional = false +python-versions = ">=3.6" + +[[package]] +name = "pydantic" +version = "1.9.2" +description = "Data validation and settings management using python type hints" +category = "main" +optional = false +python-versions = ">=3.6.1" + +[package.dependencies] +typing-extensions = ">=3.7.4.3" + +[package.extras] +dotenv = ["python-dotenv (>=0.10.4)"] +email = ["email-validator (>=1.0.3)"] + +[[package]] +name = "pyflakes" +version = "2.5.0" +description = "passive checker of Python programs" +category = "dev" +optional = false +python-versions = ">=3.6" + +[[package]] +name = "pyparsing" +version = "3.0.9" +description = "pyparsing module - Classes and methods to define and execute parsing grammars" +category = "main" +optional = false +python-versions = ">=3.6.8" + +[package.extras] +diagrams = ["jinja2", "railroad-diagrams"] + +[[package]] +name = "pytest" +version = "7.1.2" +description = "pytest: simple powerful testing with Python" +category = "dev" +optional = false +python-versions = ">=3.7" + +[package.dependencies] +atomicwrites = {version = ">=1.0", markers = "sys_platform == \"win32\""} +attrs = ">=19.2.0" +colorama = {version = "*", markers = "sys_platform == \"win32\""} +iniconfig = "*" +packaging = "*" +pluggy = ">=0.12,<2.0" +py = ">=1.8.2" +tomli = ">=1.0.0" + +[package.extras] +testing = ["argcomplete", "hypothesis (>=3.56)", "mock", "nose", "pygments (>=2.7.2)", "requests", "xmlschema"] + +[[package]] +name = "pytest-asyncio" +version = "0.19.0" +description = "Pytest support for asyncio" +category = "dev" +optional = false +python-versions = ">=3.7" + +[package.dependencies] +pytest = ">=6.1.0" + +[package.extras] +testing = ["coverage (>=6.2)", "flaky (>=3.5.0)", "hypothesis (>=5.7.1)", "mypy (>=0.931)", "pytest-trio (>=0.7.0)"] + +[[package]] +name = "pytest-cov" +version = "2.5.1" +description = "Pytest plugin for measuring coverage." +category = "dev" +optional = false +python-versions = "*" + +[package.dependencies] +coverage = ">=3.7.1" +pytest = ">=2.6.0" + +[[package]] +name = "python-dateutil" +version = "2.8.2" +description = "Extensions to the standard Python datetime module" +category = "main" +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" + +[package.dependencies] +six = ">=1.5" + +[[package]] +name = "python-slugify" +version = "6.1.2" +description = "A Python slugify application that also handles Unicode" +category = "main" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*" + +[package.dependencies] +text-unidecode = ">=1.3" + +[package.extras] +unidecode = ["Unidecode (>=1.1.1)"] + +[[package]] +name = "python-stdnum" +version = "1.15" +description = "Python module to handle standardized numbers and codes" +category = "main" +optional = false +python-versions = "*" + +[package.extras] +soap = ["zeep"] +soap-alt = ["suds"] +soap-fallback = ["pysimplesoap"] + +[[package]] +name = "pytimeparse" +version = "1.1.8" +description = "Time expression parser" +category = "main" +optional = false +python-versions = "*" + +[[package]] +name = "pytz" +version = "2022.2.1" +description = "World timezone definitions, modern and historical" +category = "main" +optional = false +python-versions = "*" + +[[package]] +name = "pywavelets" +version = "1.3.0" +description = "PyWavelets, wavelet transform module" +category = "main" +optional = false +python-versions = ">=3.7" + +[package.dependencies] +numpy = ">=1.17.3" + +[[package]] +name = "pyyaml" +version = "6.0" +description = "YAML parser and emitter for Python" +category = "main" +optional = false +python-versions = ">=3.6" + +[[package]] +name = "quart" +version = "0.14.1" +description = "A Python ASGI web microframework with the same API as Flask" +category = "main" +optional = false +python-versions = ">=3.7.0" + +[package.dependencies] +aiofiles = "*" +blinker = "*" +click = "*" +hypercorn = ">=0.7.0" +itsdangerous = "*" +jinja2 = "*" +toml = "*" +werkzeug = ">=1.0.0" + +[package.extras] +dotenv = ["python-dotenv"] + +[[package]] +name = "quart-cors" +version = "0.3.0" +description = "A Quart extension to provide Cross Origin Resource Sharing, access control, support." +category = "main" +optional = false +python-versions = ">=3.7.0" + +[package.dependencies] +Quart = ">=0.11.1" + +[[package]] +name = "requests" +version = "2.27.1" +description = "Python HTTP for Humans." +category = "main" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*" + +[package.dependencies] +certifi = ">=2017.4.17" +charset-normalizer = {version = ">=2.0.0,<2.1.0", markers = "python_version >= \"3\""} +idna = {version = ">=2.5,<4", markers = "python_version >= \"3\""} +urllib3 = ">=1.21.1,<1.27" + +[package.extras] +socks = ["PySocks (>=1.5.6,!=1.5.7)", "win-inet-pton"] +use_chardet_on_py3 = ["chardet (>=3.0.2,<5)"] + +[[package]] +name = "s3transfer" +version = "0.5.2" +description = "An Amazon S3 Transfer Manager" +category = "main" +optional = false +python-versions = ">= 3.6" + +[package.dependencies] +botocore = ">=1.12.36,<2.0a.0" + +[package.extras] +crt = ["botocore[crt] (>=1.20.29,<2.0a.0)"] + +[[package]] +name = "scipy" +version = "1.6.1" +description = "SciPy: Scientific Library for Python" +category = "main" +optional = false +python-versions = ">=3.7" + +[package.dependencies] +numpy = ">=1.16.5" + +[[package]] +name = "seaborn" +version = "0.11.2" +description = "seaborn: statistical data visualization" +category = "main" +optional = false +python-versions = ">=3.6" + +[package.dependencies] +matplotlib = ">=2.2" +numpy = ">=1.15" +pandas = ">=0.23" +scipy = ">=1.0" + +[[package]] +name = "sentry-sdk" +version = "1.0.0" +description = "Python client for Sentry (https://sentry.io)" +category = "main" +optional = false +python-versions = "*" + +[package.dependencies] +certifi = "*" +urllib3 = ">=1.10.0" + +[package.extras] +aiohttp = ["aiohttp (>=3.5)"] +beam = ["apache-beam (>=2.12)"] +bottle = ["bottle (>=0.12.13)"] +celery = ["celery (>=3)"] +chalice = ["chalice (>=1.16.0)"] +django = ["django (>=1.8)"] +falcon = ["falcon (>=1.4)"] +flask = ["blinker (>=1.1)", "flask (>=0.11)"] +pure_eval = ["asttokens", "executing", "pure-eval"] +pyspark = ["pyspark (>=2.4.4)"] +rq = ["rq (>=0.6)"] +sanic = ["sanic (>=0.8)"] +sqlalchemy = ["sqlalchemy (>=1.2)"] +tornado = ["tornado (>=5)"] + +[[package]] +name = "setuptools-scm" +version = "6.4.2" +description = "the blessed package to manage your versions by scm tags" +category = "main" +optional = false +python-versions = ">=3.6" + +[package.dependencies] +packaging = ">=20.0" +tomli = ">=1.0.0" + +[package.extras] +test = ["pytest (>=6.2)", "virtualenv (>20)"] +toml = ["setuptools (>=42)"] + +[[package]] +name = "six" +version = "1.16.0" +description = "Python 2 and 3 compatibility utilities" +category = "main" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" + +[[package]] +name = "sqlalchemy" +version = "1.4.40" +description = "Database Abstraction Library" +category = "main" +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,>=2.7" + +[package.dependencies] +greenlet = {version = "!=0.4.17", markers = "python_version >= \"3\" and (platform_machine == \"aarch64\" or platform_machine == \"ppc64le\" or platform_machine == \"x86_64\" or platform_machine == \"amd64\" or platform_machine == \"AMD64\" or platform_machine == \"win32\" or platform_machine == \"WIN32\")"} + +[package.extras] +aiomysql = ["aiomysql", "greenlet (!=0.4.17)"] +aiosqlite = ["aiosqlite", "greenlet (!=0.4.17)", "typing_extensions (!=3.10.0.1)"] +asyncio = ["greenlet (!=0.4.17)"] +asyncmy = ["asyncmy (>=0.2.3,!=0.2.4)", "greenlet (!=0.4.17)"] +mariadb_connector = ["mariadb (>=1.0.1,!=1.1.2)"] +mssql = ["pyodbc"] +mssql_pymssql = ["pymssql"] +mssql_pyodbc = ["pyodbc"] +mypy = ["mypy (>=0.910)", "sqlalchemy2-stubs"] +mysql = ["mysqlclient (>=1.4.0)", "mysqlclient (>=1.4.0,<2)"] +mysql_connector = ["mysql-connector-python"] +oracle = ["cx_oracle (>=7)", "cx_oracle (>=7,<8)"] +postgresql = ["psycopg2 (>=2.7)"] +postgresql_asyncpg = ["asyncpg", "greenlet (!=0.4.17)"] +postgresql_pg8000 = ["pg8000 (>=1.16.6,!=1.29.0)"] +postgresql_psycopg2binary = ["psycopg2-binary"] +postgresql_psycopg2cffi = ["psycopg2cffi"] +pymysql = ["pymysql", "pymysql (<1)"] +sqlcipher = ["sqlcipher3-binary"] + +[[package]] +name = "tangled-up-in-unicode" +version = "0.2.0" +description = "Access to the Unicode Character Database (UCD)" +category = "main" +optional = false +python-versions = ">=3.6" + +[[package]] +name = "text-unidecode" +version = "1.3" +description = "The most basic Text::Unidecode port" +category = "main" +optional = false +python-versions = "*" + +[[package]] +name = "toml" +version = "0.10.2" +description = "Python Library for Tom's Obvious, Minimal Language" +category = "main" +optional = false +python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" + +[[package]] +name = "tomli" +version = "2.0.1" +description = "A lil' TOML parser" +category = "main" +optional = false +python-versions = ">=3.7" + +[[package]] +name = "tqdm" +version = "4.64.0" +description = "Fast, Extensible Progress Meter" +category = "main" +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,>=2.7" + +[package.dependencies] +colorama = {version = "*", markers = "platform_system == \"Windows\""} + +[package.extras] +dev = ["py-make (>=0.1.0)", "twine", "wheel"] +notebook = ["ipywidgets (>=6)"] +slack = ["slack-sdk"] +telegram = ["requests"] + +[[package]] +name = "typing-extensions" +version = "4.3.0" +description = "Backported and Experimental Type Hints for Python 3.7+" +category = "main" +optional = false +python-versions = ">=3.7" + +[[package]] +name = "udata-event-service" +version = "0.0.8" +description = "udata event service package containing kafka utils" +category = "main" +optional = false +python-versions = "*" + +[package.dependencies] +kafka-python = "2.0.2" + +[[package]] +name = "urllib3" +version = "1.26.12" +description = "HTTP library with thread-safe connection pooling, file post, and more." +category = "main" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*, <4" + +[package.extras] +brotli = ["brotli (>=1.0.9)", "brotlicffi (>=0.8.0)", "brotlipy (>=0.6.0)"] +secure = ["certifi", "cryptography (>=1.3.4)", "idna (>=2.0.0)", "ipaddress", "pyOpenSSL (>=0.14)", "urllib3-secure-extra"] +socks = ["PySocks (>=1.5.6,!=1.5.7,<2.0)"] + +[[package]] +name = "validators" +version = "0.18.2" +description = "Python Data Validation for Humans™." +category = "main" +optional = false +python-versions = ">=3.4" + +[package.dependencies] +decorator = ">=3.4.0" +six = ">=1.4.0" + +[package.extras] +test = ["flake8 (>=2.4.0)", "isort (>=4.2.2)", "pytest (>=2.2.3)"] + +[[package]] +name = "visions" +version = "0.7.4" +description = "Visions" +category = "main" +optional = false +python-versions = ">=3.6" + +[package.dependencies] +attrs = ">=19.3.0" +imagehash = {version = "*", optional = true, markers = "extra == \"type_image_path\""} +multimethod = ">=1.4" +networkx = ">=2.4" +numpy = "*" +pandas = ">=0.25.3" +Pillow = {version = "*", optional = true, markers = "extra == \"type_image_path\""} +tangled-up-in-unicode = ">=0.0.4" + +[package.extras] +all = ["attrs (>=19.3.0)", "imagehash", "matplotlib", "multimethod (>=1.4)", "networkx (>=2.4)", "numpy", "pandas (>=0.25.3)", "pillow", "pydot", "pygraphviz", "shapely", "tangled-up-in-unicode (>=0.0.4)"] +dev = ["black (>=20.8b1)", "ipython", "isort (>=5.0.9)", "mypy (>=0.770)", "nbsphinx", "recommonmark (>=0.6.0)", "setuptools (>=46.1.3)", "sphinx-autodoc-typehints (>=1.10.3)", "sphinx-copybutton", "sphinx-rtd-theme (>=0.4.3)", "wheel (>=0.34.2)"] +plotting = ["matplotlib", "pydot", "pygraphviz"] +test = ["big-o (>=0.10.1)", "black (>=19.10b0)", "check-manifest (>=0.41)", "imagehash", "isort (>=5.0.9)", "matplotlib", "mypy (>=0.800)", "pandas", "pillow", "pre-commit", "pyarrow (>=1.0.1)", "pydot", "pytest (>=5.2.0)", "pytest-spark (>=0.6.0)", "shapely", "twine (>=3.1.1)"] +type_geometry = ["shapely"] +type_image_path = ["imagehash", "pillow"] + +[[package]] +name = "werkzeug" +version = "1.0.1" +description = "The comprehensive WSGI web application library." +category = "main" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" + +[package.extras] +dev = ["coverage", "pallets-sphinx-themes", "pytest", "pytest-timeout", "sphinx", "sphinx-issues", "tox"] +watchdog = ["watchdog"] + +[[package]] +name = "wsproto" +version = "1.2.0" +description = "WebSockets state-machine based protocol implementation" +category = "main" +optional = false +python-versions = ">=3.7.0" + +[package.dependencies] +h11 = ">=0.9.0,<1" + +[[package]] +name = "xlrd" +version = "2.0.1" +description = "Library for developers to extract data from Microsoft Excel (tm) .xls spreadsheet files" +category = "main" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*" + +[package.extras] +build = ["twine", "wheel"] +docs = ["sphinx"] +test = ["pytest", "pytest-cov"] + +[[package]] +name = "yarl" +version = "1.8.1" +description = "Yet another URL library" +category = "main" +optional = false +python-versions = ">=3.7" + +[package.dependencies] +idna = ">=2.0" +multidict = ">=4.0" + +[metadata] +lock-version = "1.1" +python-versions = ">=3.9,<4" +content-hash = "423e8b1cadacc743d465e6bf72e580281db552618c90638948a6561a7d07c76d" + +[metadata.files] +agate = [ + {file = "agate-1.6.3-py2.py3-none-any.whl", hash = "sha256:2d568fd68a8eb8b56c805a1299ba4bc30ca0434563be1bea309c9d1c1c8401f4"}, + {file = "agate-1.6.3.tar.gz", hash = "sha256:e0f2f813f7e12311a4cdccc97d6ba0a6781e9c1aa8eca0ab00d5931c0113a308"}, +] +agate-excel = [ + {file = "agate-excel-0.2.5.tar.gz", hash = "sha256:62315708433108772f7f610ca769996b468a4ead380076dbaf6ffe262831b153"}, + {file = "agate_excel-0.2.5-py2.py3-none-any.whl", hash = "sha256:8963da01e0a09438a9d2743573948c14689f43e7e01bdd9add9865e9e24e294b"}, +] +agate-sql = [ + {file = "agate-sql-0.5.8.tar.gz", hash = "sha256:581e062ae878cc087d3d0948670d46b16589df0790bf814524b0587a359f2ada"}, + {file = "agate_sql-0.5.8-py2.py3-none-any.whl", hash = "sha256:aae3d1014c3396cb0c21a21f0e41b514128bc0479b9a1627d020611f470199c3"}, +] +aiofiles = [ + {file = "aiofiles-0.8.0-py3-none-any.whl", hash = "sha256:7a973fc22b29e9962d0897805ace5856e6a566ab1f0c8e5c91ff6c866519c937"}, + {file = "aiofiles-0.8.0.tar.gz", hash = "sha256:8334f23235248a3b2e83b2c3a78a22674f39969b96397126cc93664d9a901e59"}, +] +aiohttp = [ + {file = "aiohttp-3.7.4.post0-cp36-cp36m-macosx_10_14_x86_64.whl", hash = "sha256:3cf75f7cdc2397ed4442594b935a11ed5569961333d49b7539ea741be2cc79d5"}, + {file = "aiohttp-3.7.4.post0-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:4b302b45040890cea949ad092479e01ba25911a15e648429c7c5aae9650c67a8"}, + {file = "aiohttp-3.7.4.post0-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:fe60131d21b31fd1a14bd43e6bb88256f69dfc3188b3a89d736d6c71ed43ec95"}, + {file = "aiohttp-3.7.4.post0-cp36-cp36m-manylinux2014_i686.whl", hash = "sha256:393f389841e8f2dfc86f774ad22f00923fdee66d238af89b70ea314c4aefd290"}, + {file = "aiohttp-3.7.4.post0-cp36-cp36m-manylinux2014_ppc64le.whl", hash = "sha256:c6e9dcb4cb338d91a73f178d866d051efe7c62a7166653a91e7d9fb18274058f"}, + {file = "aiohttp-3.7.4.post0-cp36-cp36m-manylinux2014_s390x.whl", hash = "sha256:5df68496d19f849921f05f14f31bd6ef53ad4b00245da3195048c69934521809"}, + {file = "aiohttp-3.7.4.post0-cp36-cp36m-manylinux2014_x86_64.whl", hash = "sha256:0563c1b3826945eecd62186f3f5c7d31abb7391fedc893b7e2b26303b5a9f3fe"}, + {file = "aiohttp-3.7.4.post0-cp36-cp36m-win32.whl", hash = "sha256:3d78619672183be860b96ed96f533046ec97ca067fd46ac1f6a09cd9b7484287"}, + {file = "aiohttp-3.7.4.post0-cp36-cp36m-win_amd64.whl", hash = "sha256:f705e12750171c0ab4ef2a3c76b9a4024a62c4103e3a55dd6f99265b9bc6fcfc"}, + {file = "aiohttp-3.7.4.post0-cp37-cp37m-macosx_10_14_x86_64.whl", hash = "sha256:230a8f7e24298dea47659251abc0fd8b3c4e38a664c59d4b89cca7f6c09c9e87"}, + {file = "aiohttp-3.7.4.post0-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:2e19413bf84934d651344783c9f5e22dee452e251cfd220ebadbed2d9931dbf0"}, + {file = "aiohttp-3.7.4.post0-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:e4b2b334e68b18ac9817d828ba44d8fcb391f6acb398bcc5062b14b2cbeac970"}, + {file = "aiohttp-3.7.4.post0-cp37-cp37m-manylinux2014_i686.whl", hash = "sha256:d012ad7911653a906425d8473a1465caa9f8dea7fcf07b6d870397b774ea7c0f"}, + {file = "aiohttp-3.7.4.post0-cp37-cp37m-manylinux2014_ppc64le.whl", hash = "sha256:40eced07f07a9e60e825554a31f923e8d3997cfc7fb31dbc1328c70826e04cde"}, + {file = "aiohttp-3.7.4.post0-cp37-cp37m-manylinux2014_s390x.whl", hash = "sha256:209b4a8ee987eccc91e2bd3ac36adee0e53a5970b8ac52c273f7f8fd4872c94c"}, + {file = "aiohttp-3.7.4.post0-cp37-cp37m-manylinux2014_x86_64.whl", hash = "sha256:14762875b22d0055f05d12abc7f7d61d5fd4fe4642ce1a249abdf8c700bf1fd8"}, + {file = "aiohttp-3.7.4.post0-cp37-cp37m-win32.whl", hash = "sha256:7615dab56bb07bff74bc865307aeb89a8bfd9941d2ef9d817b9436da3a0ea54f"}, + {file = "aiohttp-3.7.4.post0-cp37-cp37m-win_amd64.whl", hash = "sha256:d9e13b33afd39ddeb377eff2c1c4f00544e191e1d1dee5b6c51ddee8ea6f0cf5"}, + {file = "aiohttp-3.7.4.post0-cp38-cp38-macosx_10_14_x86_64.whl", hash = "sha256:547da6cacac20666422d4882cfcd51298d45f7ccb60a04ec27424d2f36ba3eaf"}, + {file = "aiohttp-3.7.4.post0-cp38-cp38-manylinux1_i686.whl", hash = "sha256:af9aa9ef5ba1fd5b8c948bb11f44891968ab30356d65fd0cc6707d989cd521df"}, + {file = "aiohttp-3.7.4.post0-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:64322071e046020e8797117b3658b9c2f80e3267daec409b350b6a7a05041213"}, + {file = "aiohttp-3.7.4.post0-cp38-cp38-manylinux2014_i686.whl", hash = "sha256:bb437315738aa441251214dad17428cafda9cdc9729499f1d6001748e1d432f4"}, + {file = "aiohttp-3.7.4.post0-cp38-cp38-manylinux2014_ppc64le.whl", hash = "sha256:e54962802d4b8b18b6207d4a927032826af39395a3bd9196a5af43fc4e60b009"}, + {file = "aiohttp-3.7.4.post0-cp38-cp38-manylinux2014_s390x.whl", hash = "sha256:a00bb73540af068ca7390e636c01cbc4f644961896fa9363154ff43fd37af2f5"}, + {file = "aiohttp-3.7.4.post0-cp38-cp38-manylinux2014_x86_64.whl", hash = "sha256:79ebfc238612123a713a457d92afb4096e2148be17df6c50fb9bf7a81c2f8013"}, + {file = "aiohttp-3.7.4.post0-cp38-cp38-win32.whl", hash = "sha256:515dfef7f869a0feb2afee66b957cc7bbe9ad0cdee45aec7fdc623f4ecd4fb16"}, + {file = "aiohttp-3.7.4.post0-cp38-cp38-win_amd64.whl", hash = "sha256:114b281e4d68302a324dd33abb04778e8557d88947875cbf4e842c2c01a030c5"}, + {file = "aiohttp-3.7.4.post0-cp39-cp39-macosx_10_14_x86_64.whl", hash = "sha256:7b18b97cf8ee5452fa5f4e3af95d01d84d86d32c5e2bfa260cf041749d66360b"}, + {file = "aiohttp-3.7.4.post0-cp39-cp39-manylinux1_i686.whl", hash = "sha256:15492a6368d985b76a2a5fdd2166cddfea5d24e69eefed4630cbaae5c81d89bd"}, + {file = "aiohttp-3.7.4.post0-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:bdb230b4943891321e06fc7def63c7aace16095be7d9cf3b1e01be2f10fba439"}, + {file = "aiohttp-3.7.4.post0-cp39-cp39-manylinux2014_i686.whl", hash = "sha256:cffe3ab27871bc3ea47df5d8f7013945712c46a3cc5a95b6bee15887f1675c22"}, + {file = "aiohttp-3.7.4.post0-cp39-cp39-manylinux2014_ppc64le.whl", hash = "sha256:f881853d2643a29e643609da57b96d5f9c9b93f62429dcc1cbb413c7d07f0e1a"}, + {file = "aiohttp-3.7.4.post0-cp39-cp39-manylinux2014_s390x.whl", hash = "sha256:a5ca29ee66f8343ed336816c553e82d6cade48a3ad702b9ffa6125d187e2dedb"}, + {file = "aiohttp-3.7.4.post0-cp39-cp39-manylinux2014_x86_64.whl", hash = "sha256:17c073de315745a1510393a96e680d20af8e67e324f70b42accbd4cb3315c9fb"}, + {file = "aiohttp-3.7.4.post0-cp39-cp39-win32.whl", hash = "sha256:932bb1ea39a54e9ea27fc9232163059a0b8855256f4052e776357ad9add6f1c9"}, + {file = "aiohttp-3.7.4.post0-cp39-cp39-win_amd64.whl", hash = "sha256:02f46fc0e3c5ac58b80d4d56eb0a7c7d97fcef69ace9326289fb9f1955e65cfe"}, + {file = "aiohttp-3.7.4.post0.tar.gz", hash = "sha256:493d3299ebe5f5a7c66b9819eacdcfbbaaf1a8e84911ddffcdc48888497afecf"}, +] +aioresponses = [ + {file = "aioresponses-0.7.3-py2.py3-none-any.whl", hash = "sha256:7b1897169062c92fa87d6ecc503ac566ac87fbfacb2504f8ca81c8035a2eb068"}, + {file = "aioresponses-0.7.3.tar.gz", hash = "sha256:2c64ed5710ee8cb4e958c569184dad12f4c9cd5939135cb38f88c6a8261cceb3"}, +] +aiosqlite = [ + {file = "aiosqlite-0.16.1-py3-none-any.whl", hash = "sha256:1df802815bb1e08a26c06d5ea9df589bcb8eec56e5f3378103b0f9b223c6703c"}, + {file = "aiosqlite-0.16.1.tar.gz", hash = "sha256:2e915463164efa65b60fd1901aceca829b6090082f03082618afca6fb9c8fdf7"}, +] +async-timeout = [ + {file = "async-timeout-3.0.1.tar.gz", hash = "sha256:0c3c816a028d47f659d6ff5c745cb2acf1f966da1fe5c19c77a70282b25f4c5f"}, + {file = "async_timeout-3.0.1-py3-none-any.whl", hash = "sha256:4291ca197d287d274d0b6cb5d6f8f8f82d434ed288f962539ff18cc9012f9ea3"}, +] +atomicwrites = [ + {file = "atomicwrites-1.4.1.tar.gz", hash = "sha256:81b2c9071a49367a7f770170e5eec8cb66567cfbbc8c73d20ce5ca4a8d71cf11"}, +] +attrs = [ + {file = "attrs-22.1.0-py2.py3-none-any.whl", hash = "sha256:86efa402f67bf2df34f51a335487cf46b1ec130d02b8d39fd248abfd30da551c"}, + {file = "attrs-22.1.0.tar.gz", hash = "sha256:29adc2665447e5191d0e7c568fde78b21f9672d344281d0c6e1ab085429b22b6"}, +] +babel = [ + {file = "Babel-2.10.3-py3-none-any.whl", hash = "sha256:ff56f4892c1c4bf0d814575ea23471c230d544203c7748e8c68f0089478d48eb"}, + {file = "Babel-2.10.3.tar.gz", hash = "sha256:7614553711ee97490f732126dc077f8d0ae084ebc6a96e23db1482afabdb2c51"}, +] +blinker = [ + {file = "blinker-1.5-py2.py3-none-any.whl", hash = "sha256:1eb563df6fdbc39eeddc177d953203f99f097e9bf0e2b8f9f3cf18b6ca425e36"}, + {file = "blinker-1.5.tar.gz", hash = "sha256:923e5e2f69c155f2cc42dafbbd70e16e3fde24d2d4aa2ab72fbe386238892462"}, +] +boto3 = [ + {file = "boto3-1.21.46-py3-none-any.whl", hash = "sha256:3b13d727854aba9dea900b6c7fa134c52396869d842460d14fab8b85b69645f7"}, + {file = "boto3-1.21.46.tar.gz", hash = "sha256:9ac902076eac82112f4536cc2606a1f597a387dbc56b250575ac2d2c64c75e20"}, +] +botocore = [ + {file = "botocore-1.24.46-py3-none-any.whl", hash = "sha256:663d8f02b98641846eb959c54c840cc33264d5f2dee5b8fc09ee8adbef0f8dcf"}, + {file = "botocore-1.24.46.tar.gz", hash = "sha256:89a203bba3c8f2299287e48a9e112e2dbe478cf67eaac26716f0e7f176446146"}, +] +cchardet = [ + {file = "cchardet-2.1.7-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:c6f70139aaf47ffb94d89db603af849b82efdf756f187cdd3e566e30976c519f"}, + {file = "cchardet-2.1.7-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:5a25f9577e9bebe1a085eec2d6fdd72b7a9dd680811bba652ea6090fb2ff472f"}, + {file = "cchardet-2.1.7-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:6b6397d8a32b976a333bdae060febd39ad5479817fabf489e5596a588ad05133"}, + {file = "cchardet-2.1.7-cp36-cp36m-manylinux2010_i686.whl", hash = "sha256:228d2533987c450f39acf7548f474dd6814c446e9d6bd228e8f1d9a2d210f10b"}, + {file = "cchardet-2.1.7-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:54341e7e1ba9dc0add4c9d23b48d3a94e2733065c13920e85895f944596f6150"}, + {file = "cchardet-2.1.7-cp36-cp36m-win32.whl", hash = "sha256:eee4f5403dc3a37a1ca9ab87db32b48dc7e190ef84601068f45397144427cc5e"}, + {file = "cchardet-2.1.7-cp36-cp36m-win_amd64.whl", hash = "sha256:f86e0566cb61dc4397297696a4a1b30f6391b50bc52b4f073507a48466b6255a"}, + {file = "cchardet-2.1.7-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:302aa443ae2526755d412c9631136bdcd1374acd08e34f527447f06f3c2ddb98"}, + {file = "cchardet-2.1.7-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:70eeae8aaf61192e9b247cf28969faef00578becd2602526ecd8ae7600d25e0e"}, + {file = "cchardet-2.1.7-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:a39526c1c526843965cec589a6f6b7c2ab07e3e56dc09a7f77a2be6a6afa4636"}, + {file = "cchardet-2.1.7-cp37-cp37m-manylinux2010_i686.whl", hash = "sha256:b154effa12886e9c18555dfc41a110f601f08d69a71809c8d908be4b1ab7314f"}, + {file = "cchardet-2.1.7-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:ec3eb5a9c475208cf52423524dcaf713c394393e18902e861f983c38eeb77f18"}, + {file = "cchardet-2.1.7-cp37-cp37m-win32.whl", hash = "sha256:50ad671e8d6c886496db62c3bd68b8d55060688c655873aa4ce25ca6105409a1"}, + {file = "cchardet-2.1.7-cp37-cp37m-win_amd64.whl", hash = "sha256:54d0b26fd0cd4099f08fb9c167600f3e83619abefeaa68ad823cc8ac1f7bcc0c"}, + {file = "cchardet-2.1.7-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:b59ddc615883835e03c26f81d5fc3671fab2d32035c87f50862de0da7d7db535"}, + {file = "cchardet-2.1.7-cp38-cp38-manylinux1_i686.whl", hash = "sha256:27a9ba87c9f99e0618e1d3081189b1217a7d110e5c5597b0b7b7c3fedd1c340a"}, + {file = "cchardet-2.1.7-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:90086e5645f8a1801350f4cc6cb5d5bf12d3fa943811bb08667744ec1ecc9ccd"}, + {file = "cchardet-2.1.7-cp38-cp38-manylinux2010_i686.whl", hash = "sha256:45456c59ec349b29628a3c6bfb86d818ec3a6fbb7eb72de4ff3bd4713681c0e3"}, + {file = "cchardet-2.1.7-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:f16517f3697569822c6d09671217fdeab61dfebc7acb5068634d6b0728b86c0b"}, + {file = "cchardet-2.1.7-cp38-cp38-win32.whl", hash = "sha256:0b859069bbb9d27c78a2c9eb997e6f4b738db2d7039a03f8792b4058d61d1109"}, + {file = "cchardet-2.1.7-cp38-cp38-win_amd64.whl", hash = "sha256:273699c4e5cd75377776501b72a7b291a988c6eec259c29505094553ee505597"}, + {file = "cchardet-2.1.7-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:48ba829badef61441e08805cfa474ccd2774be2ff44b34898f5854168c596d4d"}, + {file = "cchardet-2.1.7-cp39-cp39-manylinux1_i686.whl", hash = "sha256:bd7f262f41fd9caf5a5f09207a55861a67af6ad5c66612043ed0f81c58cdf376"}, + {file = "cchardet-2.1.7-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:fdac1e4366d0579fff056d1280b8dc6348be964fda8ebb627c0269e097ab37fa"}, + {file = "cchardet-2.1.7-cp39-cp39-manylinux2010_i686.whl", hash = "sha256:80e6faae75ecb9be04a7b258dc4750d459529debb6b8dee024745b7b5a949a34"}, + {file = "cchardet-2.1.7-cp39-cp39-manylinux2010_x86_64.whl", hash = "sha256:c96aee9ebd1147400e608a3eff97c44f49811f8904e5a43069d55603ac4d8c97"}, + {file = "cchardet-2.1.7-cp39-cp39-win32.whl", hash = "sha256:2309ff8fc652b0fc3c0cff5dbb172530c7abb92fe9ba2417c9c0bcf688463c1c"}, + {file = "cchardet-2.1.7-cp39-cp39-win_amd64.whl", hash = "sha256:24974b3e40fee9e7557bb352be625c39ec6f50bc2053f44a3d1191db70b51675"}, + {file = "cchardet-2.1.7.tar.gz", hash = "sha256:c428b6336545053c2589f6caf24ea32276c6664cb86db817e03a94c60afa0eaf"}, +] +certifi = [ + {file = "certifi-2022.6.15-py3-none-any.whl", hash = "sha256:fe86415d55e84719d75f8b69414f6438ac3547d2078ab91b67e779ef69378412"}, + {file = "certifi-2022.6.15.tar.gz", hash = "sha256:84c85a9078b11105f04f3036a9482ae10e4621616db313fe045dd24743a0820d"}, +] +chardet = [ + {file = "chardet-4.0.0-py2.py3-none-any.whl", hash = "sha256:f864054d66fd9118f2e67044ac8981a54775ec5b67aed0441892edb553d21da5"}, + {file = "chardet-4.0.0.tar.gz", hash = "sha256:0d6f53a15db4120f2b08c94f11e7d93d2c911ee118b6b30a04ec3ee8310179fa"}, +] +charset-normalizer = [ + {file = "charset-normalizer-2.0.12.tar.gz", hash = "sha256:2857e29ff0d34db842cd7ca3230549d1a697f96ee6d3fb071cfa6c7393832597"}, + {file = "charset_normalizer-2.0.12-py3-none-any.whl", hash = "sha256:6881edbebdb17b39b4eaaa821b438bf6eddffb4468cf344f09f89def34a8b1df"}, +] +click = [ + {file = "click-8.1.3-py3-none-any.whl", hash = "sha256:bb4d8133cb15a609f44e8213d9b391b0809795062913b383c62be0ee95b1db48"}, + {file = "click-8.1.3.tar.gz", hash = "sha256:7682dc8afb30297001674575ea00d1814d808d6a36af415a82bd481d37ba7b8e"}, +] +click-default-group = [ + {file = "click-default-group-1.2.2.tar.gz", hash = "sha256:d9560e8e8dfa44b3562fbc9425042a0fd6d21956fcc2db0077f63f34253ab904"}, +] +colorama = [ + {file = "colorama-0.4.5-py2.py3-none-any.whl", hash = "sha256:854bf444933e37f5824ae7bfc1e98d5bce2ebe4160d46b5edf346a89358e99da"}, + {file = "colorama-0.4.5.tar.gz", hash = "sha256:e6c6b4334fc50988a639d9b98aa429a0b57da6e17b9a44f0451f930b6967b7a4"}, +] +coverage = [ + {file = "coverage-6.4.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e7b4da9bafad21ea45a714d3ea6f3e1679099e420c8741c74905b92ee9bfa7cc"}, + {file = "coverage-6.4.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:fde17bc42e0716c94bf19d92e4c9f5a00c5feb401f5bc01101fdf2a8b7cacf60"}, + {file = "coverage-6.4.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cdbb0d89923c80dbd435b9cf8bba0ff55585a3cdb28cbec65f376c041472c60d"}, + {file = "coverage-6.4.4-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:67f9346aeebea54e845d29b487eb38ec95f2ecf3558a3cffb26ee3f0dcc3e760"}, + {file = "coverage-6.4.4-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:42c499c14efd858b98c4e03595bf914089b98400d30789511577aa44607a1b74"}, + {file = "coverage-6.4.4-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:c35cca192ba700979d20ac43024a82b9b32a60da2f983bec6c0f5b84aead635c"}, + {file = "coverage-6.4.4-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:9cc4f107009bca5a81caef2fca843dbec4215c05e917a59dec0c8db5cff1d2aa"}, + {file = "coverage-6.4.4-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:5f444627b3664b80d078c05fe6a850dd711beeb90d26731f11d492dcbadb6973"}, + {file = "coverage-6.4.4-cp310-cp310-win32.whl", hash = "sha256:66e6df3ac4659a435677d8cd40e8eb1ac7219345d27c41145991ee9bf4b806a0"}, + {file = "coverage-6.4.4-cp310-cp310-win_amd64.whl", hash = "sha256:35ef1f8d8a7a275aa7410d2f2c60fa6443f4a64fae9be671ec0696a68525b875"}, + {file = "coverage-6.4.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c1328d0c2f194ffda30a45f11058c02410e679456276bfa0bbe0b0ee87225fac"}, + {file = "coverage-6.4.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:61b993f3998ee384935ee423c3d40894e93277f12482f6e777642a0141f55782"}, + {file = "coverage-6.4.4-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d5dd4b8e9cd0deb60e6fcc7b0647cbc1da6c33b9e786f9c79721fd303994832f"}, + {file = "coverage-6.4.4-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7026f5afe0d1a933685d8f2169d7c2d2e624f6255fb584ca99ccca8c0e966fd7"}, + {file = "coverage-6.4.4-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:9c7b9b498eb0c0d48b4c2abc0e10c2d78912203f972e0e63e3c9dc21f15abdaa"}, + {file = "coverage-6.4.4-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:ee2b2fb6eb4ace35805f434e0f6409444e1466a47f620d1d5763a22600f0f892"}, + {file = "coverage-6.4.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:ab066f5ab67059d1f1000b5e1aa8bbd75b6ed1fc0014559aea41a9eb66fc2ce0"}, + {file = "coverage-6.4.4-cp311-cp311-win32.whl", hash = "sha256:9d6e1f3185cbfd3d91ac77ea065d85d5215d3dfa45b191d14ddfcd952fa53796"}, + {file = "coverage-6.4.4-cp311-cp311-win_amd64.whl", hash = "sha256:e3d3c4cc38b2882f9a15bafd30aec079582b819bec1b8afdbde8f7797008108a"}, + {file = "coverage-6.4.4-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:a095aa0a996ea08b10580908e88fbaf81ecf798e923bbe64fb98d1807db3d68a"}, + {file = "coverage-6.4.4-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ef6f44409ab02e202b31a05dd6666797f9de2aa2b4b3534e9d450e42dea5e817"}, + {file = "coverage-6.4.4-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4b7101938584d67e6f45f0015b60e24a95bf8dea19836b1709a80342e01b472f"}, + {file = "coverage-6.4.4-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:14a32ec68d721c3d714d9b105c7acf8e0f8a4f4734c811eda75ff3718570b5e3"}, + {file = "coverage-6.4.4-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:6a864733b22d3081749450466ac80698fe39c91cb6849b2ef8752fd7482011f3"}, + {file = "coverage-6.4.4-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:08002f9251f51afdcc5e3adf5d5d66bb490ae893d9e21359b085f0e03390a820"}, + {file = "coverage-6.4.4-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:a3b2752de32c455f2521a51bd3ffb53c5b3ae92736afde67ce83477f5c1dd928"}, + {file = "coverage-6.4.4-cp37-cp37m-win32.whl", hash = "sha256:f855b39e4f75abd0dfbcf74a82e84ae3fc260d523fcb3532786bcbbcb158322c"}, + {file = "coverage-6.4.4-cp37-cp37m-win_amd64.whl", hash = "sha256:ee6ae6bbcac0786807295e9687169fba80cb0617852b2fa118a99667e8e6815d"}, + {file = "coverage-6.4.4-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:564cd0f5b5470094df06fab676c6d77547abfdcb09b6c29c8a97c41ad03b103c"}, + {file = "coverage-6.4.4-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:cbbb0e4cd8ddcd5ef47641cfac97d8473ab6b132dd9a46bacb18872828031685"}, + {file = "coverage-6.4.4-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6113e4df2fa73b80f77663445be6d567913fb3b82a86ceb64e44ae0e4b695de1"}, + {file = "coverage-6.4.4-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8d032bfc562a52318ae05047a6eb801ff31ccee172dc0d2504614e911d8fa83e"}, + {file = "coverage-6.4.4-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e431e305a1f3126477abe9a184624a85308da8edf8486a863601d58419d26ffa"}, + {file = "coverage-6.4.4-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:cf2afe83a53f77aec067033199797832617890e15bed42f4a1a93ea24794ae3e"}, + {file = "coverage-6.4.4-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:783bc7c4ee524039ca13b6d9b4186a67f8e63d91342c713e88c1865a38d0892a"}, + {file = "coverage-6.4.4-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:ff934ced84054b9018665ca3967fc48e1ac99e811f6cc99ea65978e1d384454b"}, + {file = "coverage-6.4.4-cp38-cp38-win32.whl", hash = "sha256:e1fabd473566fce2cf18ea41171d92814e4ef1495e04471786cbc943b89a3781"}, + {file = "coverage-6.4.4-cp38-cp38-win_amd64.whl", hash = "sha256:4179502f210ebed3ccfe2f78bf8e2d59e50b297b598b100d6c6e3341053066a2"}, + {file = "coverage-6.4.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:98c0b9e9b572893cdb0a00e66cf961a238f8d870d4e1dc8e679eb8bdc2eb1b86"}, + {file = "coverage-6.4.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:fc600f6ec19b273da1d85817eda339fb46ce9eef3e89f220055d8696e0a06908"}, + {file = "coverage-6.4.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7a98d6bf6d4ca5c07a600c7b4e0c5350cd483c85c736c522b786be90ea5bac4f"}, + {file = "coverage-6.4.4-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:01778769097dbd705a24e221f42be885c544bb91251747a8a3efdec6eb4788f2"}, + {file = "coverage-6.4.4-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dfa0b97eb904255e2ab24166071b27408f1f69c8fbda58e9c0972804851e0558"}, + {file = "coverage-6.4.4-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:fcbe3d9a53e013f8ab88734d7e517eb2cd06b7e689bedf22c0eb68db5e4a0a19"}, + {file = "coverage-6.4.4-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:15e38d853ee224e92ccc9a851457fb1e1f12d7a5df5ae44544ce7863691c7a0d"}, + {file = "coverage-6.4.4-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:6913dddee2deff8ab2512639c5168c3e80b3ebb0f818fed22048ee46f735351a"}, + {file = "coverage-6.4.4-cp39-cp39-win32.whl", hash = "sha256:354df19fefd03b9a13132fa6643527ef7905712109d9c1c1903f2133d3a4e145"}, + {file = "coverage-6.4.4-cp39-cp39-win_amd64.whl", hash = "sha256:1238b08f3576201ebf41f7c20bf59baa0d05da941b123c6656e42cdb668e9827"}, + {file = "coverage-6.4.4-pp36.pp37.pp38-none-any.whl", hash = "sha256:f67cf9f406cf0d2f08a3515ce2db5b82625a7257f88aad87904674def6ddaec1"}, + {file = "coverage-6.4.4.tar.gz", hash = "sha256:e16c45b726acb780e1e6f88b286d3c10b3914ab03438f32117c4aa52d7f30d58"}, +] +cycler = [ + {file = "cycler-0.11.0-py3-none-any.whl", hash = "sha256:3a27e95f763a428a739d2add979fa7494c912a32c17c4c38c4d5f082cad165a3"}, + {file = "cycler-0.11.0.tar.gz", hash = "sha256:9c87405839a19696e837b3b818fed3f5f69f16f1eec1a1ad77e043dcea9c772f"}, +] +decorator = [ + {file = "decorator-5.1.1-py3-none-any.whl", hash = "sha256:b8c3f85900b9dc423225913c5aace94729fe1fa9763b38939a95226f02d37186"}, + {file = "decorator-5.1.1.tar.gz", hash = "sha256:637996211036b6385ef91435e4fae22989472f9d571faba8927ba8253acbc330"}, +] +et-xmlfile = [ + {file = "et_xmlfile-1.1.0-py3-none-any.whl", hash = "sha256:a2ba85d1d6a74ef63837eed693bcb89c3f752169b0e3e7ae5b16ca5e1b3deada"}, + {file = "et_xmlfile-1.1.0.tar.gz", hash = "sha256:8eb9e2bc2f8c97e37a2dc85a09ecdcdec9d8a396530a6d5a33b30b9a92da0c5c"}, +] +flake8 = [ + {file = "flake8-5.0.4-py2.py3-none-any.whl", hash = "sha256:7a1cf6b73744f5806ab95e526f6f0d8c01c66d7bbe349562d22dfca20610b248"}, + {file = "flake8-5.0.4.tar.gz", hash = "sha256:6fbe320aad8d6b95cec8b8e47bc933004678dc63095be98528b7bdd2a9f510db"}, +] +fonttools = [ + {file = "fonttools-4.37.1-py3-none-any.whl", hash = "sha256:fff6b752e326c15756c819fe2fe7ceab69f96a1dbcfe8911d0941cdb49905007"}, + {file = "fonttools-4.37.1.zip", hash = "sha256:4606e1a88ee1f6699d182fea9511bd9a8a915d913eab4584e5226da1180fcce7"}, +] +future = [ + {file = "future-0.18.2.tar.gz", hash = "sha256:b1bead90b70cf6ec3f0710ae53a525360fa360d306a86583adc6bf83a4db537d"}, +] +greenlet = [ + {file = "greenlet-1.1.3-cp27-cp27m-macosx_10_14_x86_64.whl", hash = "sha256:8c287ae7ac921dfde88b1c125bd9590b7ec3c900c2d3db5197f1286e144e712b"}, + {file = "greenlet-1.1.3-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:870a48007872d12e95a996fca3c03a64290d3ea2e61076aa35d3b253cf34cd32"}, + {file = "greenlet-1.1.3-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:7c5227963409551ae4a6938beb70d56bf1918c554a287d3da6853526212fbe0a"}, + {file = "greenlet-1.1.3-cp27-cp27m-win32.whl", hash = "sha256:9fae214f6c43cd47f7bef98c56919b9222481e833be2915f6857a1e9e8a15318"}, + {file = "greenlet-1.1.3-cp27-cp27m-win_amd64.whl", hash = "sha256:de431765bd5fe62119e0bc6bc6e7b17ac53017ae1782acf88fcf6b7eae475a49"}, + {file = "greenlet-1.1.3-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:510c3b15587afce9800198b4b142202b323bf4b4b5f9d6c79cb9a35e5e3c30d2"}, + {file = "greenlet-1.1.3-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:9951dcbd37850da32b2cb6e391f621c1ee456191c6ae5528af4a34afe357c30e"}, + {file = "greenlet-1.1.3-cp310-cp310-macosx_10_15_x86_64.whl", hash = "sha256:07c58e169bbe1e87b8bbf15a5c1b779a7616df9fd3e61cadc9d691740015b4f8"}, + {file = "greenlet-1.1.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:df02fdec0c533301497acb0bc0f27f479a3a63dcdc3a099ae33a902857f07477"}, + {file = "greenlet-1.1.3-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9c88e134d51d5e82315a7c32b914a58751b7353eb5268dbd02eabf020b4c4700"}, + {file = "greenlet-1.1.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7b41d19c0cfe5c259fe6c539fd75051cd39a5d33d05482f885faf43f7f5e7d26"}, + {file = "greenlet-1.1.3-cp310-cp310-win_amd64.whl", hash = "sha256:6f5d4b2280ceea76c55c893827961ed0a6eadd5a584a7c4e6e6dd7bc10dfdd96"}, + {file = "greenlet-1.1.3-cp311-cp311-macosx_10_15_x86_64.whl", hash = "sha256:184416e481295832350a4bf731ba619a92f5689bf5d0fa4341e98b98b1265bd7"}, + {file = "greenlet-1.1.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dd0404d154084a371e6d2bafc787201612a1359c2dee688ae334f9118aa0bf47"}, + {file = "greenlet-1.1.3-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7a43bbfa9b6cfdfaeefbd91038dde65ea2c421dc387ed171613df340650874f2"}, + {file = "greenlet-1.1.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ce5b64dfe8d0cca407d88b0ee619d80d4215a2612c1af8c98a92180e7109f4b5"}, + {file = "greenlet-1.1.3-cp35-cp35m-macosx_10_14_x86_64.whl", hash = "sha256:903fa5716b8fbb21019268b44f73f3748c41d1a30d71b4a49c84b642c2fed5fa"}, + {file = "greenlet-1.1.3-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:0118817c9341ef2b0f75f5af79ac377e4da6ff637e5ee4ac91802c0e379dadb4"}, + {file = "greenlet-1.1.3-cp35-cp35m-manylinux2010_x86_64.whl", hash = "sha256:466ce0928e33421ee84ae04c4ac6f253a3a3e6b8d600a79bd43fd4403e0a7a76"}, + {file = "greenlet-1.1.3-cp35-cp35m-win32.whl", hash = "sha256:65ad1a7a463a2a6f863661329a944a5802c7129f7ad33583dcc11069c17e622c"}, + {file = "greenlet-1.1.3-cp35-cp35m-win_amd64.whl", hash = "sha256:7532a46505470be30cbf1dbadb20379fb481244f1ca54207d7df3bf0bbab6a20"}, + {file = "greenlet-1.1.3-cp36-cp36m-macosx_10_14_x86_64.whl", hash = "sha256:caff52cb5cd7626872d9696aee5b794abe172804beb7db52eed1fd5824b63910"}, + {file = "greenlet-1.1.3-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:db41f3845eb579b544c962864cce2c2a0257fe30f0f1e18e51b1e8cbb4e0ac6d"}, + {file = "greenlet-1.1.3-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:e8533f5111704d75de3139bf0b8136d3a6c1642c55c067866fa0a51c2155ee33"}, + {file = "greenlet-1.1.3-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9537e4baf0db67f382eb29255a03154fcd4984638303ff9baaa738b10371fa57"}, + {file = "greenlet-1.1.3-cp36-cp36m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f8bfd36f368efe0ab2a6aa3db7f14598aac454b06849fb633b762ddbede1db90"}, + {file = "greenlet-1.1.3-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b0877a9a2129a2c56a2eae2da016743db7d9d6a05d5e1c198f1b7808c602a30e"}, + {file = "greenlet-1.1.3-cp36-cp36m-win32.whl", hash = "sha256:88b04e12c9b041a1e0bcb886fec709c488192638a9a7a3677513ac6ba81d8e79"}, + {file = "greenlet-1.1.3-cp36-cp36m-win_amd64.whl", hash = "sha256:4f166b4aca8d7d489e82d74627a7069ab34211ef5ebb57c300ec4b9337b60fc0"}, + {file = "greenlet-1.1.3-cp37-cp37m-macosx_10_15_x86_64.whl", hash = "sha256:cd16a89efe3a003029c87ff19e9fba635864e064da646bc749fc1908a4af18f3"}, + {file = "greenlet-1.1.3-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:5b756e6730ea59b2745072e28ad27f4c837084688e6a6b3633c8b1e509e6ae0e"}, + {file = "greenlet-1.1.3-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:9b2f7d0408ddeb8ea1fd43d3db79a8cefaccadd2a812f021333b338ed6b10aba"}, + {file = "greenlet-1.1.3-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:44b4817c34c9272c65550b788913620f1fdc80362b209bc9d7dd2f40d8793080"}, + {file = "greenlet-1.1.3-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d58a5a71c4c37354f9e0c24c9c8321f0185f6945ef027460b809f4bb474bfe41"}, + {file = "greenlet-1.1.3-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1dd51d2650e70c6c4af37f454737bf4a11e568945b27f74b471e8e2a9fd21268"}, + {file = "greenlet-1.1.3-cp37-cp37m-win32.whl", hash = "sha256:048d2bed76c2aa6de7af500ae0ea51dd2267aec0e0f2a436981159053d0bc7cc"}, + {file = "greenlet-1.1.3-cp37-cp37m-win_amd64.whl", hash = "sha256:77e41db75f9958f2083e03e9dd39da12247b3430c92267df3af77c83d8ff9eed"}, + {file = "greenlet-1.1.3-cp38-cp38-macosx_10_15_x86_64.whl", hash = "sha256:1626185d938d7381631e48e6f7713e8d4b964be246073e1a1d15c2f061ac9f08"}, + {file = "greenlet-1.1.3-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:1ec2779774d8e42ed0440cf8bc55540175187e8e934f2be25199bf4ed948cd9e"}, + {file = "greenlet-1.1.3-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:f2f908239b7098799b8845e5936c2ccb91d8c2323be02e82f8dcb4a80dcf4a25"}, + {file = "greenlet-1.1.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0b181e9aa6cb2f5ec0cacc8cee6e5a3093416c841ba32c185c30c160487f0380"}, + {file = "greenlet-1.1.3-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2cf45e339cabea16c07586306a31cfcc5a3b5e1626d365714d283732afed6809"}, + {file = "greenlet-1.1.3-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6200a11f003ec26815f7e3d2ded01b43a3810be3528dd760d2f1fa777490c3cd"}, + {file = "greenlet-1.1.3-cp38-cp38-win32.whl", hash = "sha256:db5b25265010a1b3dca6a174a443a0ed4c4ab12d5e2883a11c97d6e6d59b12f9"}, + {file = "greenlet-1.1.3-cp38-cp38-win_amd64.whl", hash = "sha256:095a980288fe05adf3d002fbb180c99bdcf0f930e220aa66fcd56e7914a38202"}, + {file = "greenlet-1.1.3-cp39-cp39-macosx_10_15_x86_64.whl", hash = "sha256:cbc1eb55342cbac8f7ec159088d54e2cfdd5ddf61c87b8bbe682d113789331b2"}, + {file = "greenlet-1.1.3-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:694ffa7144fa5cc526c8f4512665003a39fa09ef00d19bbca5c8d3406db72fbe"}, + {file = "greenlet-1.1.3-cp39-cp39-manylinux2010_x86_64.whl", hash = "sha256:aa741c1a8a8cc25eb3a3a01a62bdb5095a773d8c6a86470bde7f607a447e7905"}, + {file = "greenlet-1.1.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a3a669f11289a8995d24fbfc0e63f8289dd03c9aaa0cc8f1eab31d18ca61a382"}, + {file = "greenlet-1.1.3-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:76a53bfa10b367ee734b95988bd82a9a5f0038a25030f9f23bbbc005010ca600"}, + {file = "greenlet-1.1.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2fb0aa7f6996879551fd67461d5d3ab0c3c0245da98be90c89fcb7a18d437403"}, + {file = "greenlet-1.1.3-cp39-cp39-win32.whl", hash = "sha256:5fbe1ab72b998ca77ceabbae63a9b2e2dc2d963f4299b9b278252ddba142d3f1"}, + {file = "greenlet-1.1.3-cp39-cp39-win_amd64.whl", hash = "sha256:ffe73f9e7aea404722058405ff24041e59d31ca23d1da0895af48050a07b6932"}, + {file = "greenlet-1.1.3.tar.gz", hash = "sha256:bcb6c6dd1d6be6d38d6db283747d07fda089ff8c559a835236560a4410340455"}, +] +h11 = [ + {file = "h11-0.13.0-py3-none-any.whl", hash = "sha256:8ddd78563b633ca55346c8cd41ec0af27d3c79931828beffb46ce70a379e7442"}, + {file = "h11-0.13.0.tar.gz", hash = "sha256:70813c1135087a248a4d38cc0e1a0181ffab2188141a93eaf567940c3957ff06"}, +] +h2 = [ + {file = "h2-4.1.0-py3-none-any.whl", hash = "sha256:03a46bcf682256c95b5fd9e9a99c1323584c3eec6440d379b9903d709476bc6d"}, + {file = "h2-4.1.0.tar.gz", hash = "sha256:a83aca08fbe7aacb79fec788c9c0bac936343560ed9ec18b82a13a12c28d2abb"}, +] +hpack = [ + {file = "hpack-4.0.0-py3-none-any.whl", hash = "sha256:84a076fad3dc9a9f8063ccb8041ef100867b1878b25ef0ee63847a5d53818a6c"}, + {file = "hpack-4.0.0.tar.gz", hash = "sha256:fc41de0c63e687ebffde81187a948221294896f6bdc0ae2312708df339430095"}, +] +htmlmin = [ + {file = "htmlmin-0.1.12.tar.gz", hash = "sha256:50c1ef4630374a5d723900096a961cff426dff46b48f34d194a81bbe14eca178"}, +] +hypercorn = [ + {file = "Hypercorn-0.13.2-py3-none-any.whl", hash = "sha256:ca18f91ab3fa823cbe9e949738f9f2cc07027cd647c80d8f93e4b1a2a175f112"}, + {file = "Hypercorn-0.13.2.tar.gz", hash = "sha256:6307be5cbdf6ba411967d4661202dc4f79bd511b5d318bc4eed88b09418427f8"}, +] +hyperframe = [ + {file = "hyperframe-6.0.1-py3-none-any.whl", hash = "sha256:0ec6bafd80d8ad2195c4f03aacba3a8265e57bc4cff261e802bf39970ed02a15"}, + {file = "hyperframe-6.0.1.tar.gz", hash = "sha256:ae510046231dc8e9ecb1a6586f63d2347bf4c8905914aa84ba585ae85f28a914"}, +] +idna = [ + {file = "idna-3.3-py3-none-any.whl", hash = "sha256:84d9dd047ffa80596e0f246e2eab0b391788b0503584e8945f2368256d2735ff"}, + {file = "idna-3.3.tar.gz", hash = "sha256:9d643ff0a55b762d5cdb124b8eaa99c66322e2157b69160bc32796e824360e6d"}, +] +imagehash = [ + {file = "ImageHash-4.2.1.tar.gz", hash = "sha256:a4af957814bc9832d9241247ff03f76e778f890c18147900b4540af124e93011"}, +] +iniconfig = [ + {file = "iniconfig-1.1.1-py2.py3-none-any.whl", hash = "sha256:011e24c64b7f47f6ebd835bb12a743f2fbe9a26d4cecaa7f53bc4f35ee9da8b3"}, + {file = "iniconfig-1.1.1.tar.gz", hash = "sha256:bc3af051d7d14b2ee5ef9969666def0cd1a000e121eaea580d4a313df4b37f32"}, +] +isodate = [ + {file = "isodate-0.6.1-py2.py3-none-any.whl", hash = "sha256:0751eece944162659049d35f4f549ed815792b38793f07cf73381c1c87cbed96"}, + {file = "isodate-0.6.1.tar.gz", hash = "sha256:48c5881de7e8b0a0d648cb024c8062dc84e7b840ed81e864c7614fd3c127bde9"}, +] +itsdangerous = [ + {file = "itsdangerous-2.1.2-py3-none-any.whl", hash = "sha256:2c2349112351b88699d8d4b6b075022c0808887cb7ad10069318a8b0bc88db44"}, + {file = "itsdangerous-2.1.2.tar.gz", hash = "sha256:5dbbc68b317e5e42f327f9021763545dc3fc3bfe22e6deb96aaf1fc38874156a"}, +] +jinja2 = [ + {file = "Jinja2-3.0.3-py3-none-any.whl", hash = "sha256:077ce6014f7b40d03b47d1f1ca4b0fc8328a692bd284016f806ed0eaca390ad8"}, + {file = "Jinja2-3.0.3.tar.gz", hash = "sha256:611bb273cd68f3b993fabdc4064fc858c5b47a973cb5aa7999ec1ba405c87cd7"}, +] +jmespath = [ + {file = "jmespath-1.0.1-py3-none-any.whl", hash = "sha256:02e2e4cc71b5bcab88332eebf907519190dd9e6e82107fa7f83b1003a6252980"}, + {file = "jmespath-1.0.1.tar.gz", hash = "sha256:90261b206d6defd58fdd5e85f478bf633a2901798906be2ad389150c5c60edbe"}, +] +joblib = [ + {file = "joblib-1.1.0-py2.py3-none-any.whl", hash = "sha256:f21f109b3c7ff9d95f8387f752d0d9c34a02aa2f7060c2135f465da0e5160ff6"}, + {file = "joblib-1.1.0.tar.gz", hash = "sha256:4158fcecd13733f8be669be0683b96ebdbbd38d23559f54dca7205aea1bf1e35"}, +] +kafka-python = [ + {file = "kafka-python-2.0.2.tar.gz", hash = "sha256:04dfe7fea2b63726cd6f3e79a2d86e709d608d74406638c5da33a01d45a9d7e3"}, + {file = "kafka_python-2.0.2-py2.py3-none-any.whl", hash = "sha256:2d92418c7cb1c298fa6c7f0fb3519b520d0d7526ac6cb7ae2a4fc65a51a94b6e"}, +] +kiwisolver = [ + {file = "kiwisolver-1.4.4-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:2f5e60fabb7343a836360c4f0919b8cd0d6dbf08ad2ca6b9cf90bf0c76a3c4f6"}, + {file = "kiwisolver-1.4.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:10ee06759482c78bdb864f4109886dff7b8a56529bc1609d4f1112b93fe6423c"}, + {file = "kiwisolver-1.4.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c79ebe8f3676a4c6630fd3f777f3cfecf9289666c84e775a67d1d358578dc2e3"}, + {file = "kiwisolver-1.4.4-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:abbe9fa13da955feb8202e215c4018f4bb57469b1b78c7a4c5c7b93001699938"}, + {file = "kiwisolver-1.4.4-cp310-cp310-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:7577c1987baa3adc4b3c62c33bd1118c3ef5c8ddef36f0f2c950ae0b199e100d"}, + {file = "kiwisolver-1.4.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f8ad8285b01b0d4695102546b342b493b3ccc6781fc28c8c6a1bb63e95d22f09"}, + {file = "kiwisolver-1.4.4-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8ed58b8acf29798b036d347791141767ccf65eee7f26bde03a71c944449e53de"}, + {file = "kiwisolver-1.4.4-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a68b62a02953b9841730db7797422f983935aeefceb1679f0fc85cbfbd311c32"}, + {file = "kiwisolver-1.4.4-cp310-cp310-win32.whl", hash = "sha256:e92a513161077b53447160b9bd8f522edfbed4bd9759e4c18ab05d7ef7e49408"}, + {file = "kiwisolver-1.4.4-cp310-cp310-win_amd64.whl", hash = "sha256:3fe20f63c9ecee44560d0e7f116b3a747a5d7203376abeea292ab3152334d004"}, + {file = "kiwisolver-1.4.4-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:e0ea21f66820452a3f5d1655f8704a60d66ba1191359b96541eaf457710a5fc6"}, + {file = "kiwisolver-1.4.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:bc9db8a3efb3e403e4ecc6cd9489ea2bac94244f80c78e27c31dcc00d2790ac2"}, + {file = "kiwisolver-1.4.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d5b61785a9ce44e5a4b880272baa7cf6c8f48a5180c3e81c59553ba0cb0821ca"}, + {file = "kiwisolver-1.4.4-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c2dbb44c3f7e6c4d3487b31037b1bdbf424d97687c1747ce4ff2895795c9bf69"}, + {file = "kiwisolver-1.4.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6295ecd49304dcf3bfbfa45d9a081c96509e95f4b9d0eb7ee4ec0530c4a96514"}, + {file = "kiwisolver-1.4.4-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4bd472dbe5e136f96a4b18f295d159d7f26fd399136f5b17b08c4e5f498cd494"}, + {file = "kiwisolver-1.4.4-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:bf7d9fce9bcc4752ca4a1b80aabd38f6d19009ea5cbda0e0856983cf6d0023f5"}, + {file = "kiwisolver-1.4.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:78d6601aed50c74e0ef02f4204da1816147a6d3fbdc8b3872d263338a9052c51"}, + {file = "kiwisolver-1.4.4-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:877272cf6b4b7e94c9614f9b10140e198d2186363728ed0f701c6eee1baec1da"}, + {file = "kiwisolver-1.4.4-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:db608a6757adabb32f1cfe6066e39b3706d8c3aa69bbc353a5b61edad36a5cb4"}, + {file = "kiwisolver-1.4.4-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:5853eb494c71e267912275e5586fe281444eb5e722de4e131cddf9d442615626"}, + {file = "kiwisolver-1.4.4-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:f0a1dbdb5ecbef0d34eb77e56fcb3e95bbd7e50835d9782a45df81cc46949750"}, + {file = "kiwisolver-1.4.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:283dffbf061a4ec60391d51e6155e372a1f7a4f5b15d59c8505339454f8989e4"}, + {file = "kiwisolver-1.4.4-cp311-cp311-win32.whl", hash = "sha256:d06adcfa62a4431d404c31216f0f8ac97397d799cd53800e9d3efc2fbb3cf14e"}, + {file = "kiwisolver-1.4.4-cp311-cp311-win_amd64.whl", hash = "sha256:e7da3fec7408813a7cebc9e4ec55afed2d0fd65c4754bc376bf03498d4e92686"}, + {file = "kiwisolver-1.4.4-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:62ac9cc684da4cf1778d07a89bf5f81b35834cb96ca523d3a7fb32509380cbf6"}, + {file = "kiwisolver-1.4.4-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:41dae968a94b1ef1897cb322b39360a0812661dba7c682aa45098eb8e193dbdf"}, + {file = "kiwisolver-1.4.4-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:02f79693ec433cb4b5f51694e8477ae83b3205768a6fb48ffba60549080e295b"}, + {file = "kiwisolver-1.4.4-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d0611a0a2a518464c05ddd5a3a1a0e856ccc10e67079bb17f265ad19ab3c7597"}, + {file = "kiwisolver-1.4.4-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:db5283d90da4174865d520e7366801a93777201e91e79bacbac6e6927cbceede"}, + {file = "kiwisolver-1.4.4-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:1041feb4cda8708ce73bb4dcb9ce1ccf49d553bf87c3954bdfa46f0c3f77252c"}, + {file = "kiwisolver-1.4.4-cp37-cp37m-win32.whl", hash = "sha256:a553dadda40fef6bfa1456dc4be49b113aa92c2a9a9e8711e955618cd69622e3"}, + {file = "kiwisolver-1.4.4-cp37-cp37m-win_amd64.whl", hash = "sha256:03baab2d6b4a54ddbb43bba1a3a2d1627e82d205c5cf8f4c924dc49284b87166"}, + {file = "kiwisolver-1.4.4-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:841293b17ad704d70c578f1f0013c890e219952169ce8a24ebc063eecf775454"}, + {file = "kiwisolver-1.4.4-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:f4f270de01dd3e129a72efad823da90cc4d6aafb64c410c9033aba70db9f1ff0"}, + {file = "kiwisolver-1.4.4-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:f9f39e2f049db33a908319cf46624a569b36983c7c78318e9726a4cb8923b26c"}, + {file = "kiwisolver-1.4.4-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c97528e64cb9ebeff9701e7938653a9951922f2a38bd847787d4a8e498cc83ae"}, + {file = "kiwisolver-1.4.4-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1d1573129aa0fd901076e2bfb4275a35f5b7aa60fbfb984499d661ec950320b0"}, + {file = "kiwisolver-1.4.4-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ad881edc7ccb9d65b0224f4e4d05a1e85cf62d73aab798943df6d48ab0cd79a1"}, + {file = "kiwisolver-1.4.4-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:b428ef021242344340460fa4c9185d0b1f66fbdbfecc6c63eff4b7c29fad429d"}, + {file = "kiwisolver-1.4.4-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:2e407cb4bd5a13984a6c2c0fe1845e4e41e96f183e5e5cd4d77a857d9693494c"}, + {file = "kiwisolver-1.4.4-cp38-cp38-win32.whl", hash = "sha256:75facbe9606748f43428fc91a43edb46c7ff68889b91fa31f53b58894503a191"}, + {file = "kiwisolver-1.4.4-cp38-cp38-win_amd64.whl", hash = "sha256:5bce61af018b0cb2055e0e72e7d65290d822d3feee430b7b8203d8a855e78766"}, + {file = "kiwisolver-1.4.4-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:8c808594c88a025d4e322d5bb549282c93c8e1ba71b790f539567932722d7bd8"}, + {file = "kiwisolver-1.4.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:f0a71d85ecdd570ded8ac3d1c0f480842f49a40beb423bb8014539a9f32a5897"}, + {file = "kiwisolver-1.4.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:b533558eae785e33e8c148a8d9921692a9fe5aa516efbdff8606e7d87b9d5824"}, + {file = "kiwisolver-1.4.4-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:efda5fc8cc1c61e4f639b8067d118e742b812c930f708e6667a5ce0d13499e29"}, + {file = "kiwisolver-1.4.4-cp39-cp39-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:7c43e1e1206cd421cd92e6b3280d4385d41d7166b3ed577ac20444b6995a445f"}, + {file = "kiwisolver-1.4.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bc8d3bd6c72b2dd9decf16ce70e20abcb3274ba01b4e1c96031e0c4067d1e7cd"}, + {file = "kiwisolver-1.4.4-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4ea39b0ccc4f5d803e3337dd46bcce60b702be4d86fd0b3d7531ef10fd99a1ac"}, + {file = "kiwisolver-1.4.4-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:968f44fdbf6dd757d12920d63b566eeb4d5b395fd2d00d29d7ef00a00582aac9"}, + {file = "kiwisolver-1.4.4-cp39-cp39-win32.whl", hash = "sha256:da7e547706e69e45d95e116e6939488d62174e033b763ab1496b4c29b76fabea"}, + {file = "kiwisolver-1.4.4-cp39-cp39-win_amd64.whl", hash = "sha256:ba59c92039ec0a66103b1d5fe588fa546373587a7d68f5c96f743c3396afc04b"}, + {file = "kiwisolver-1.4.4-pp37-pypy37_pp73-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:91672bacaa030f92fc2f43b620d7b337fd9a5af28b0d6ed3f77afc43c4a64b5a"}, + {file = "kiwisolver-1.4.4-pp37-pypy37_pp73-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:787518a6789009c159453da4d6b683f468ef7a65bbde796bcea803ccf191058d"}, + {file = "kiwisolver-1.4.4-pp37-pypy37_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:da152d8cdcab0e56e4f45eb08b9aea6455845ec83172092f09b0e077ece2cf7a"}, + {file = "kiwisolver-1.4.4-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:ecb1fa0db7bf4cff9dac752abb19505a233c7f16684c5826d1f11ebd9472b871"}, + {file = "kiwisolver-1.4.4-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:28bc5b299f48150b5f822ce68624e445040595a4ac3d59251703779836eceff9"}, + {file = "kiwisolver-1.4.4-pp38-pypy38_pp73-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:81e38381b782cc7e1e46c4e14cd997ee6040768101aefc8fa3c24a4cc58e98f8"}, + {file = "kiwisolver-1.4.4-pp38-pypy38_pp73-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:2a66fdfb34e05b705620dd567f5a03f239a088d5a3f321e7b6ac3239d22aa286"}, + {file = "kiwisolver-1.4.4-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:872b8ca05c40d309ed13eb2e582cab0c5a05e81e987ab9c521bf05ad1d5cf5cb"}, + {file = "kiwisolver-1.4.4-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:70e7c2e7b750585569564e2e5ca9845acfaa5da56ac46df68414f29fea97be9f"}, + {file = "kiwisolver-1.4.4-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:9f85003f5dfa867e86d53fac6f7e6f30c045673fa27b603c397753bebadc3008"}, + {file = "kiwisolver-1.4.4-pp39-pypy39_pp73-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2e307eb9bd99801f82789b44bb45e9f541961831c7311521b13a6c85afc09767"}, + {file = "kiwisolver-1.4.4-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b1792d939ec70abe76f5054d3f36ed5656021dcad1322d1cc996d4e54165cef9"}, + {file = "kiwisolver-1.4.4-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f6cb459eea32a4e2cf18ba5fcece2dbdf496384413bc1bae15583f19e567f3b2"}, + {file = "kiwisolver-1.4.4-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:36dafec3d6d6088d34e2de6b85f9d8e2324eb734162fba59d2ba9ed7a2043d5b"}, + {file = "kiwisolver-1.4.4.tar.gz", hash = "sha256:d41997519fcba4a1e46eb4a2fe31bc12f0ff957b2b81bac28db24744f333e955"}, +] +leather = [ + {file = "leather-0.3.4-py2.py3-none-any.whl", hash = "sha256:5e741daee96e9f1e9e06081b8c8a10c4ac199301a0564cdd99b09df15b4603d2"}, + {file = "leather-0.3.4.tar.gz", hash = "sha256:b43e21c8fa46b2679de8449f4d953c06418666dc058ce41055ee8a8d3bb40918"}, +] +markupsafe = [ + {file = "MarkupSafe-2.1.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:86b1f75c4e7c2ac2ccdaec2b9022845dbb81880ca318bb7a0a01fbf7813e3812"}, + {file = "MarkupSafe-2.1.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:f121a1420d4e173a5d96e47e9a0c0dcff965afdf1626d28de1460815f7c4ee7a"}, + {file = "MarkupSafe-2.1.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a49907dd8420c5685cfa064a1335b6754b74541bbb3706c259c02ed65b644b3e"}, + {file = "MarkupSafe-2.1.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:10c1bfff05d95783da83491be968e8fe789263689c02724e0c691933c52994f5"}, + {file = "MarkupSafe-2.1.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b7bd98b796e2b6553da7225aeb61f447f80a1ca64f41d83612e6139ca5213aa4"}, + {file = "MarkupSafe-2.1.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:b09bf97215625a311f669476f44b8b318b075847b49316d3e28c08e41a7a573f"}, + {file = "MarkupSafe-2.1.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:694deca8d702d5db21ec83983ce0bb4b26a578e71fbdbd4fdcd387daa90e4d5e"}, + {file = "MarkupSafe-2.1.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:efc1913fd2ca4f334418481c7e595c00aad186563bbc1ec76067848c7ca0a933"}, + {file = "MarkupSafe-2.1.1-cp310-cp310-win32.whl", hash = "sha256:4a33dea2b688b3190ee12bd7cfa29d39c9ed176bda40bfa11099a3ce5d3a7ac6"}, + {file = "MarkupSafe-2.1.1-cp310-cp310-win_amd64.whl", hash = "sha256:dda30ba7e87fbbb7eab1ec9f58678558fd9a6b8b853530e176eabd064da81417"}, + {file = "MarkupSafe-2.1.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:671cd1187ed5e62818414afe79ed29da836dde67166a9fac6d435873c44fdd02"}, + {file = "MarkupSafe-2.1.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3799351e2336dc91ea70b034983ee71cf2f9533cdff7c14c90ea126bfd95d65a"}, + {file = "MarkupSafe-2.1.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e72591e9ecd94d7feb70c1cbd7be7b3ebea3f548870aa91e2732960fa4d57a37"}, + {file = "MarkupSafe-2.1.1-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6fbf47b5d3728c6aea2abb0589b5d30459e369baa772e0f37a0320185e87c980"}, + {file = "MarkupSafe-2.1.1-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:d5ee4f386140395a2c818d149221149c54849dfcfcb9f1debfe07a8b8bd63f9a"}, + {file = "MarkupSafe-2.1.1-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:bcb3ed405ed3222f9904899563d6fc492ff75cce56cba05e32eff40e6acbeaa3"}, + {file = "MarkupSafe-2.1.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:e1c0b87e09fa55a220f058d1d49d3fb8df88fbfab58558f1198e08c1e1de842a"}, + {file = "MarkupSafe-2.1.1-cp37-cp37m-win32.whl", hash = "sha256:8dc1c72a69aa7e082593c4a203dcf94ddb74bb5c8a731e4e1eb68d031e8498ff"}, + {file = "MarkupSafe-2.1.1-cp37-cp37m-win_amd64.whl", hash = "sha256:97a68e6ada378df82bc9f16b800ab77cbf4b2fada0081794318520138c088e4a"}, + {file = "MarkupSafe-2.1.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:e8c843bbcda3a2f1e3c2ab25913c80a3c5376cd00c6e8c4a86a89a28c8dc5452"}, + {file = "MarkupSafe-2.1.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:0212a68688482dc52b2d45013df70d169f542b7394fc744c02a57374a4207003"}, + {file = "MarkupSafe-2.1.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8e576a51ad59e4bfaac456023a78f6b5e6e7651dcd383bcc3e18d06f9b55d6d1"}, + {file = "MarkupSafe-2.1.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4b9fe39a2ccc108a4accc2676e77da025ce383c108593d65cc909add5c3bd601"}, + {file = "MarkupSafe-2.1.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:96e37a3dc86e80bf81758c152fe66dbf60ed5eca3d26305edf01892257049925"}, + {file = "MarkupSafe-2.1.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:6d0072fea50feec76a4c418096652f2c3238eaa014b2f94aeb1d56a66b41403f"}, + {file = "MarkupSafe-2.1.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:089cf3dbf0cd6c100f02945abeb18484bd1ee57a079aefd52cffd17fba910b88"}, + {file = "MarkupSafe-2.1.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:6a074d34ee7a5ce3effbc526b7083ec9731bb3cbf921bbe1d3005d4d2bdb3a63"}, + {file = "MarkupSafe-2.1.1-cp38-cp38-win32.whl", hash = "sha256:421be9fbf0ffe9ffd7a378aafebbf6f4602d564d34be190fc19a193232fd12b1"}, + {file = "MarkupSafe-2.1.1-cp38-cp38-win_amd64.whl", hash = "sha256:fc7b548b17d238737688817ab67deebb30e8073c95749d55538ed473130ec0c7"}, + {file = "MarkupSafe-2.1.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:e04e26803c9c3851c931eac40c695602c6295b8d432cbe78609649ad9bd2da8a"}, + {file = "MarkupSafe-2.1.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:b87db4360013327109564f0e591bd2a3b318547bcef31b468a92ee504d07ae4f"}, + {file = "MarkupSafe-2.1.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:99a2a507ed3ac881b975a2976d59f38c19386d128e7a9a18b7df6fff1fd4c1d6"}, + {file = "MarkupSafe-2.1.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:56442863ed2b06d19c37f94d999035e15ee982988920e12a5b4ba29b62ad1f77"}, + {file = "MarkupSafe-2.1.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3ce11ee3f23f79dbd06fb3d63e2f6af7b12db1d46932fe7bd8afa259a5996603"}, + {file = "MarkupSafe-2.1.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:33b74d289bd2f5e527beadcaa3f401e0df0a89927c1559c8566c066fa4248ab7"}, + {file = "MarkupSafe-2.1.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:43093fb83d8343aac0b1baa75516da6092f58f41200907ef92448ecab8825135"}, + {file = "MarkupSafe-2.1.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:8e3dcf21f367459434c18e71b2a9532d96547aef8a871872a5bd69a715c15f96"}, + {file = "MarkupSafe-2.1.1-cp39-cp39-win32.whl", hash = "sha256:d4306c36ca495956b6d568d276ac11fdd9c30a36f1b6eb928070dc5360b22e1c"}, + {file = "MarkupSafe-2.1.1-cp39-cp39-win_amd64.whl", hash = "sha256:46d00d6cfecdde84d40e572d63735ef81423ad31184100411e6e3388d405e247"}, + {file = "MarkupSafe-2.1.1.tar.gz", hash = "sha256:7f91197cc9e48f989d12e4e6fbc46495c446636dfc81b9ccf50bb0ec74b91d4b"}, +] +matplotlib = [ + {file = "matplotlib-3.5.3-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:a206a1b762b39398efea838f528b3a6d60cdb26fe9d58b48265787e29cd1d693"}, + {file = "matplotlib-3.5.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:cd45a6f3e93a780185f70f05cf2a383daed13c3489233faad83e81720f7ede24"}, + {file = "matplotlib-3.5.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:d62880e1f60e5a30a2a8484432bcb3a5056969dc97258d7326ad465feb7ae069"}, + {file = "matplotlib-3.5.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9ab29589cef03bc88acfa3a1490359000c18186fc30374d8aa77d33cc4a51a4a"}, + {file = "matplotlib-3.5.3-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2886cc009f40e2984c083687251821f305d811d38e3df8ded414265e4583f0c5"}, + {file = "matplotlib-3.5.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c995f7d9568f18b5db131ab124c64e51b6820a92d10246d4f2b3f3a66698a15b"}, + {file = "matplotlib-3.5.3-cp310-cp310-win32.whl", hash = "sha256:6bb93a0492d68461bd458eba878f52fdc8ac7bdb6c4acdfe43dba684787838c2"}, + {file = "matplotlib-3.5.3-cp310-cp310-win_amd64.whl", hash = "sha256:2e6d184ebe291b9e8f7e78bbab7987d269c38ea3e062eace1fe7d898042ef804"}, + {file = "matplotlib-3.5.3-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:6ea6aef5c4338e58d8d376068e28f80a24f54e69f09479d1c90b7172bad9f25b"}, + {file = "matplotlib-3.5.3-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:839d47b8ead7ad9669aaacdbc03f29656dc21f0d41a6fea2d473d856c39c8b1c"}, + {file = "matplotlib-3.5.3-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:3b4fa56159dc3c7f9250df88f653f085068bcd32dcd38e479bba58909254af7f"}, + {file = "matplotlib-3.5.3-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:94ff86af56a3869a4ae26a9637a849effd7643858a1a04dd5ee50e9ab75069a7"}, + {file = "matplotlib-3.5.3-cp37-cp37m-win32.whl", hash = "sha256:35a8ad4dddebd51f94c5d24bec689ec0ec66173bf614374a1244c6241c1595e0"}, + {file = "matplotlib-3.5.3-cp37-cp37m-win_amd64.whl", hash = "sha256:43e9d3fa077bf0cc95ded13d331d2156f9973dce17c6f0c8b49ccd57af94dbd9"}, + {file = "matplotlib-3.5.3-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:22227c976ad4dc8c5a5057540421f0d8708c6560744ad2ad638d48e2984e1dbc"}, + {file = "matplotlib-3.5.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:bf618a825deb6205f015df6dfe6167a5d9b351203b03fab82043ae1d30f16511"}, + {file = "matplotlib-3.5.3-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:9befa5954cdbc085e37d974ff6053da269474177921dd61facdad8023c4aeb51"}, + {file = "matplotlib-3.5.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f3840c280ebc87a48488a46f760ea1c0c0c83fcf7abbe2e6baf99d033fd35fd8"}, + {file = "matplotlib-3.5.3-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:dacddf5bfcec60e3f26ec5c0ae3d0274853a258b6c3fc5ef2f06a8eb23e042be"}, + {file = "matplotlib-3.5.3-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:b428076a55fb1c084c76cb93e68006f27d247169f056412607c5c88828d08f88"}, + {file = "matplotlib-3.5.3-cp38-cp38-win32.whl", hash = "sha256:874df7505ba820e0400e7091199decf3ff1fde0583652120c50cd60d5820ca9a"}, + {file = "matplotlib-3.5.3-cp38-cp38-win_amd64.whl", hash = "sha256:b28de401d928890187c589036857a270a032961411934bdac4cf12dde3d43094"}, + {file = "matplotlib-3.5.3-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:3211ba82b9f1518d346f6309df137b50c3dc4421b4ed4815d1d7eadc617f45a1"}, + {file = "matplotlib-3.5.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:6fe807e8a22620b4cd95cfbc795ba310dc80151d43b037257250faf0bfcd82bc"}, + {file = "matplotlib-3.5.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:5c096363b206a3caf43773abebdbb5a23ea13faef71d701b21a9c27fdcef72f4"}, + {file = "matplotlib-3.5.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0bcdfcb0f976e1bac6721d7d457c17be23cf7501f977b6a38f9d38a3762841f7"}, + {file = "matplotlib-3.5.3-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:1e64ac9be9da6bfff0a732e62116484b93b02a0b4d4b19934fb4f8e7ad26ad6a"}, + {file = "matplotlib-3.5.3-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:73dd93dc35c85dece610cca8358003bf0760d7986f70b223e2306b4ea6d1406b"}, + {file = "matplotlib-3.5.3-cp39-cp39-win32.whl", hash = "sha256:879c7e5fce4939c6aa04581dfe08d57eb6102a71f2e202e3314d5fbc072fd5a0"}, + {file = "matplotlib-3.5.3-cp39-cp39-win_amd64.whl", hash = "sha256:ab8d26f07fe64f6f6736d635cce7bfd7f625320490ed5bfc347f2cdb4fae0e56"}, + {file = "matplotlib-3.5.3-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:99482b83ebf4eb6d5fc6813d7aacdefdd480f0d9c0b52dcf9f1cc3b2c4b3361a"}, + {file = "matplotlib-3.5.3-pp37-pypy37_pp73-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:f814504e459c68118bf2246a530ed953ebd18213dc20e3da524174d84ed010b2"}, + {file = "matplotlib-3.5.3-pp37-pypy37_pp73-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:57f1b4e69f438a99bb64d7f2c340db1b096b41ebaa515cf61ea72624279220ce"}, + {file = "matplotlib-3.5.3-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:d2484b350bf3d32cae43f85dcfc89b3ed7bd2bcd781ef351f93eb6fb2cc483f9"}, + {file = "matplotlib-3.5.3.tar.gz", hash = "sha256:339cac48b80ddbc8bfd05daae0a3a73414651a8596904c2a881cfd1edb65f26c"}, +] +mccabe = [ + {file = "mccabe-0.7.0-py2.py3-none-any.whl", hash = "sha256:6c2d30ab6be0e4a46919781807b4f0d834ebdd6c6e3dca0bda5a15f863427b6e"}, + {file = "mccabe-0.7.0.tar.gz", hash = "sha256:348e0240c33b60bbdf4e523192ef919f28cb2c3d7d5c7794f74009290f236325"}, +] +missingno = [ + {file = "missingno-0.5.1-py3-none-any.whl", hash = "sha256:74e8fa1ea68c9482479a9429009856ae6cc64725f085092429950e30a8d78f55"}, + {file = "missingno-0.5.1.tar.gz", hash = "sha256:22e1735a9213df7425e76123ebcc627d11a2608a1d725b90c6a2d7329db718db"}, +] +multidict = [ + {file = "multidict-6.0.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:0b9e95a740109c6047602f4db4da9949e6c5945cefbad34a1299775ddc9a62e2"}, + {file = "multidict-6.0.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:ac0e27844758d7177989ce406acc6a83c16ed4524ebc363c1f748cba184d89d3"}, + {file = "multidict-6.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:041b81a5f6b38244b34dc18c7b6aba91f9cdaf854d9a39e5ff0b58e2b5773b9c"}, + {file = "multidict-6.0.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5fdda29a3c7e76a064f2477c9aab1ba96fd94e02e386f1e665bca1807fc5386f"}, + {file = "multidict-6.0.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3368bf2398b0e0fcbf46d85795adc4c259299fec50c1416d0f77c0a843a3eed9"}, + {file = "multidict-6.0.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f4f052ee022928d34fe1f4d2bc743f32609fb79ed9c49a1710a5ad6b2198db20"}, + {file = "multidict-6.0.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:225383a6603c086e6cef0f2f05564acb4f4d5f019a4e3e983f572b8530f70c88"}, + {file = "multidict-6.0.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:50bd442726e288e884f7be9071016c15a8742eb689a593a0cac49ea093eef0a7"}, + {file = "multidict-6.0.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:47e6a7e923e9cada7c139531feac59448f1f47727a79076c0b1ee80274cd8eee"}, + {file = "multidict-6.0.2-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:0556a1d4ea2d949efe5fd76a09b4a82e3a4a30700553a6725535098d8d9fb672"}, + {file = "multidict-6.0.2-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:626fe10ac87851f4cffecee161fc6f8f9853f0f6f1035b59337a51d29ff3b4f9"}, + {file = "multidict-6.0.2-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:8064b7c6f0af936a741ea1efd18690bacfbae4078c0c385d7c3f611d11f0cf87"}, + {file = "multidict-6.0.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:2d36e929d7f6a16d4eb11b250719c39560dd70545356365b494249e2186bc389"}, + {file = "multidict-6.0.2-cp310-cp310-win32.whl", hash = "sha256:fcb91630817aa8b9bc4a74023e4198480587269c272c58b3279875ed7235c293"}, + {file = "multidict-6.0.2-cp310-cp310-win_amd64.whl", hash = "sha256:8cbf0132f3de7cc6c6ce00147cc78e6439ea736cee6bca4f068bcf892b0fd658"}, + {file = "multidict-6.0.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:05f6949d6169878a03e607a21e3b862eaf8e356590e8bdae4227eedadacf6e51"}, + {file = "multidict-6.0.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e2c2e459f7050aeb7c1b1276763364884595d47000c1cddb51764c0d8976e608"}, + {file = "multidict-6.0.2-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d0509e469d48940147e1235d994cd849a8f8195e0bca65f8f5439c56e17872a3"}, + {file = "multidict-6.0.2-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:514fe2b8d750d6cdb4712346a2c5084a80220821a3e91f3f71eec11cf8d28fd4"}, + {file = "multidict-6.0.2-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:19adcfc2a7197cdc3987044e3f415168fc5dc1f720c932eb1ef4f71a2067e08b"}, + {file = "multidict-6.0.2-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b9d153e7f1f9ba0b23ad1568b3b9e17301e23b042c23870f9ee0522dc5cc79e8"}, + {file = "multidict-6.0.2-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:aef9cc3d9c7d63d924adac329c33835e0243b5052a6dfcbf7732a921c6e918ba"}, + {file = "multidict-6.0.2-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:4571f1beddff25f3e925eea34268422622963cd8dc395bb8778eb28418248e43"}, + {file = "multidict-6.0.2-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:d48b8ee1d4068561ce8033d2c344cf5232cb29ee1a0206a7b828c79cbc5982b8"}, + {file = "multidict-6.0.2-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:45183c96ddf61bf96d2684d9fbaf6f3564d86b34cb125761f9a0ef9e36c1d55b"}, + {file = "multidict-6.0.2-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:75bdf08716edde767b09e76829db8c1e5ca9d8bb0a8d4bd94ae1eafe3dac5e15"}, + {file = "multidict-6.0.2-cp37-cp37m-win32.whl", hash = "sha256:a45e1135cb07086833ce969555df39149680e5471c04dfd6a915abd2fc3f6dbc"}, + {file = "multidict-6.0.2-cp37-cp37m-win_amd64.whl", hash = "sha256:6f3cdef8a247d1eafa649085812f8a310e728bdf3900ff6c434eafb2d443b23a"}, + {file = "multidict-6.0.2-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:0327292e745a880459ef71be14e709aaea2f783f3537588fb4ed09b6c01bca60"}, + {file = "multidict-6.0.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:e875b6086e325bab7e680e4316d667fc0e5e174bb5611eb16b3ea121c8951b86"}, + {file = "multidict-6.0.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:feea820722e69451743a3d56ad74948b68bf456984d63c1a92e8347b7b88452d"}, + {file = "multidict-6.0.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9cc57c68cb9139c7cd6fc39f211b02198e69fb90ce4bc4a094cf5fe0d20fd8b0"}, + {file = "multidict-6.0.2-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:497988d6b6ec6ed6f87030ec03280b696ca47dbf0648045e4e1d28b80346560d"}, + {file = "multidict-6.0.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:89171b2c769e03a953d5969b2f272efa931426355b6c0cb508022976a17fd376"}, + {file = "multidict-6.0.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:684133b1e1fe91eda8fa7447f137c9490a064c6b7f392aa857bba83a28cfb693"}, + {file = "multidict-6.0.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:fd9fc9c4849a07f3635ccffa895d57abce554b467d611a5009ba4f39b78a8849"}, + {file = "multidict-6.0.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:e07c8e79d6e6fd37b42f3250dba122053fddb319e84b55dd3a8d6446e1a7ee49"}, + {file = "multidict-6.0.2-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:4070613ea2227da2bfb2c35a6041e4371b0af6b0be57f424fe2318b42a748516"}, + {file = "multidict-6.0.2-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:47fbeedbf94bed6547d3aa632075d804867a352d86688c04e606971595460227"}, + {file = "multidict-6.0.2-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:5774d9218d77befa7b70d836004a768fb9aa4fdb53c97498f4d8d3f67bb9cfa9"}, + {file = "multidict-6.0.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:2957489cba47c2539a8eb7ab32ff49101439ccf78eab724c828c1a54ff3ff98d"}, + {file = "multidict-6.0.2-cp38-cp38-win32.whl", hash = "sha256:e5b20e9599ba74391ca0cfbd7b328fcc20976823ba19bc573983a25b32e92b57"}, + {file = "multidict-6.0.2-cp38-cp38-win_amd64.whl", hash = "sha256:8004dca28e15b86d1b1372515f32eb6f814bdf6f00952699bdeb541691091f96"}, + {file = "multidict-6.0.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:2e4a0785b84fb59e43c18a015ffc575ba93f7d1dbd272b4cdad9f5134b8a006c"}, + {file = "multidict-6.0.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:6701bf8a5d03a43375909ac91b6980aea74b0f5402fbe9428fc3f6edf5d9677e"}, + {file = "multidict-6.0.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:a007b1638e148c3cfb6bf0bdc4f82776cef0ac487191d093cdc316905e504071"}, + {file = "multidict-6.0.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:07a017cfa00c9890011628eab2503bee5872f27144936a52eaab449be5eaf032"}, + {file = "multidict-6.0.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c207fff63adcdf5a485969131dc70e4b194327666b7e8a87a97fbc4fd80a53b2"}, + {file = "multidict-6.0.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:373ba9d1d061c76462d74e7de1c0c8e267e9791ee8cfefcf6b0b2495762c370c"}, + {file = "multidict-6.0.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bfba7c6d5d7c9099ba21f84662b037a0ffd4a5e6b26ac07d19e423e6fdf965a9"}, + {file = "multidict-6.0.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:19d9bad105dfb34eb539c97b132057a4e709919ec4dd883ece5838bcbf262b80"}, + {file = "multidict-6.0.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:de989b195c3d636ba000ee4281cd03bb1234635b124bf4cd89eeee9ca8fcb09d"}, + {file = "multidict-6.0.2-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:7c40b7bbece294ae3a87c1bc2abff0ff9beef41d14188cda94ada7bcea99b0fb"}, + {file = "multidict-6.0.2-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:d16cce709ebfadc91278a1c005e3c17dd5f71f5098bfae1035149785ea6e9c68"}, + {file = "multidict-6.0.2-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:a2c34a93e1d2aa35fbf1485e5010337c72c6791407d03aa5f4eed920343dd360"}, + {file = "multidict-6.0.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:feba80698173761cddd814fa22e88b0661e98cb810f9f986c54aa34d281e4937"}, + {file = "multidict-6.0.2-cp39-cp39-win32.whl", hash = "sha256:23b616fdc3c74c9fe01d76ce0d1ce872d2d396d8fa8e4899398ad64fb5aa214a"}, + {file = "multidict-6.0.2-cp39-cp39-win_amd64.whl", hash = "sha256:4bae31803d708f6f15fd98be6a6ac0b6958fcf68fda3c77a048a4f9073704aae"}, + {file = "multidict-6.0.2.tar.gz", hash = "sha256:5ff3bd75f38e4c43f1f470f2df7a4d430b821c4ce22be384e1459cb57d6bb013"}, +] +multimethod = [ + {file = "multimethod-1.8-py3-none-any.whl", hash = "sha256:ebff0b254d9373b587a99fdd5c238fc0e76861b802704a56d9a71d78aa7d097f"}, + {file = "multimethod-1.8.tar.gz", hash = "sha256:10f79f40c35c7cc87c40efa753960900429705e0c08078a084136cb8ce67b840"}, +] +networkx = [ + {file = "networkx-2.8.6-py3-none-any.whl", hash = "sha256:2a30822761f34d56b9a370d96a4bf4827a535f5591a4078a453425caeba0c5bb"}, + {file = "networkx-2.8.6.tar.gz", hash = "sha256:bd2b7730300860cbd2dafe8e5af89ff5c9a65c3975b352799d87a6238b4301a6"}, +] +numpy = [ + {file = "numpy-1.23.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e603ca1fb47b913942f3e660a15e55a9ebca906857edfea476ae5f0fe9b457d5"}, + {file = "numpy-1.23.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:633679a472934b1c20a12ed0c9a6c9eb167fbb4cb89031939bfd03dd9dbc62b8"}, + {file = "numpy-1.23.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:17e5226674f6ea79e14e3b91bfbc153fdf3ac13f5cc54ee7bc8fdbe820a32da0"}, + {file = "numpy-1.23.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bdc02c0235b261925102b1bd586579b7158e9d0d07ecb61148a1799214a4afd5"}, + {file = "numpy-1.23.2-cp310-cp310-win32.whl", hash = "sha256:df28dda02c9328e122661f399f7655cdcbcf22ea42daa3650a26bce08a187450"}, + {file = "numpy-1.23.2-cp310-cp310-win_amd64.whl", hash = "sha256:8ebf7e194b89bc66b78475bd3624d92980fca4e5bb86dda08d677d786fefc414"}, + {file = "numpy-1.23.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:dc76bca1ca98f4b122114435f83f1fcf3c0fe48e4e6f660e07996abf2f53903c"}, + {file = "numpy-1.23.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:ecfdd68d334a6b97472ed032b5b37a30d8217c097acfff15e8452c710e775524"}, + {file = "numpy-1.23.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5593f67e66dea4e237f5af998d31a43e447786b2154ba1ad833676c788f37cde"}, + {file = "numpy-1.23.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ac987b35df8c2a2eab495ee206658117e9ce867acf3ccb376a19e83070e69418"}, + {file = "numpy-1.23.2-cp311-cp311-win32.whl", hash = "sha256:d98addfd3c8728ee8b2c49126f3c44c703e2b005d4a95998e2167af176a9e722"}, + {file = "numpy-1.23.2-cp311-cp311-win_amd64.whl", hash = "sha256:8ecb818231afe5f0f568c81f12ce50f2b828ff2b27487520d85eb44c71313b9e"}, + {file = "numpy-1.23.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:909c56c4d4341ec8315291a105169d8aae732cfb4c250fbc375a1efb7a844f8f"}, + {file = "numpy-1.23.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:8247f01c4721479e482cc2f9f7d973f3f47810cbc8c65e38fd1bbd3141cc9842"}, + {file = "numpy-1.23.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b8b97a8a87cadcd3f94659b4ef6ec056261fa1e1c3317f4193ac231d4df70215"}, + {file = "numpy-1.23.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bd5b7ccae24e3d8501ee5563e82febc1771e73bd268eef82a1e8d2b4d556ae66"}, + {file = "numpy-1.23.2-cp38-cp38-win32.whl", hash = "sha256:9b83d48e464f393d46e8dd8171687394d39bc5abfe2978896b77dc2604e8635d"}, + {file = "numpy-1.23.2-cp38-cp38-win_amd64.whl", hash = "sha256:dec198619b7dbd6db58603cd256e092bcadef22a796f778bf87f8592b468441d"}, + {file = "numpy-1.23.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:4f41f5bf20d9a521f8cab3a34557cd77b6f205ab2116651f12959714494268b0"}, + {file = "numpy-1.23.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:806cc25d5c43e240db709875e947076b2826f47c2c340a5a2f36da5bb10c58d6"}, + {file = "numpy-1.23.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8f9d84a24889ebb4c641a9b99e54adb8cab50972f0166a3abc14c3b93163f074"}, + {file = "numpy-1.23.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c403c81bb8ffb1c993d0165a11493fd4bf1353d258f6997b3ee288b0a48fce77"}, + {file = "numpy-1.23.2-cp39-cp39-win32.whl", hash = "sha256:cf8c6aed12a935abf2e290860af8e77b26a042eb7f2582ff83dc7ed5f963340c"}, + {file = "numpy-1.23.2-cp39-cp39-win_amd64.whl", hash = "sha256:5e28cd64624dc2354a349152599e55308eb6ca95a13ce6a7d5679ebff2962913"}, + {file = "numpy-1.23.2-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:806970e69106556d1dd200e26647e9bee5e2b3f1814f9da104a943e8d548ca38"}, + {file = "numpy-1.23.2-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2bd879d3ca4b6f39b7770829f73278b7c5e248c91d538aab1e506c628353e47f"}, + {file = "numpy-1.23.2-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:be6b350dfbc7f708d9d853663772a9310783ea58f6035eec649fb9c4371b5389"}, + {file = "numpy-1.23.2.tar.gz", hash = "sha256:b78d00e48261fbbd04aa0d7427cf78d18401ee0abd89c7559bbf422e5b1c7d01"}, +] +olefile = [ + {file = "olefile-0.46.zip", hash = "sha256:133b031eaf8fd2c9399b78b8bc5b8fcbe4c31e85295749bb17a87cba8f3c3964"}, +] +openpyxl = [ + {file = "openpyxl-3.0.10-py2.py3-none-any.whl", hash = "sha256:0ab6d25d01799f97a9464630abacbb34aafecdcaa0ef3cba6d6b3499867d0355"}, + {file = "openpyxl-3.0.10.tar.gz", hash = "sha256:e47805627aebcf860edb4edf7987b1309c1b3632f3750538ed962bbcc3bd7449"}, +] +packaging = [ + {file = "packaging-21.3-py3-none-any.whl", hash = "sha256:ef103e05f519cdc783ae24ea4e2e0f508a9c99b2d4969652eed6a2e1ea5bd522"}, + {file = "packaging-21.3.tar.gz", hash = "sha256:dd47c42927d89ab911e606518907cc2d3a1f38bbd026385970643f9c5b8ecfeb"}, +] +pandas = [ + {file = "pandas-1.4.3-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:d51674ed8e2551ef7773820ef5dab9322be0828629f2cbf8d1fc31a0c4fed640"}, + {file = "pandas-1.4.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:16ad23db55efcc93fa878f7837267973b61ea85d244fc5ff0ccbcfa5638706c5"}, + {file = "pandas-1.4.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:958a0588149190c22cdebbc0797e01972950c927a11a900fe6c2296f207b1d6f"}, + {file = "pandas-1.4.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e48fbb64165cda451c06a0f9e4c7a16b534fcabd32546d531b3c240ce2844112"}, + {file = "pandas-1.4.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6f803320c9da732cc79210d7e8cc5c8019aad512589c910c66529eb1b1818230"}, + {file = "pandas-1.4.3-cp310-cp310-win_amd64.whl", hash = "sha256:2893e923472a5e090c2d5e8db83e8f907364ec048572084c7d10ef93546be6d1"}, + {file = "pandas-1.4.3-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:24ea75f47bbd5574675dae21d51779a4948715416413b30614c1e8b480909f81"}, + {file = "pandas-1.4.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:d5ebc990bd34f4ac3c73a2724c2dcc9ee7bf1ce6cf08e87bb25c6ad33507e318"}, + {file = "pandas-1.4.3-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:d6c0106415ff1a10c326c49bc5dd9ea8b9897a6ca0c8688eb9c30ddec49535ef"}, + {file = "pandas-1.4.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:78b00429161ccb0da252229bcda8010b445c4bf924e721265bec5a6e96a92e92"}, + {file = "pandas-1.4.3-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6dfbf16b1ea4f4d0ee11084d9c026340514d1d30270eaa82a9f1297b6c8ecbf0"}, + {file = "pandas-1.4.3-cp38-cp38-win32.whl", hash = "sha256:48350592665ea3cbcd07efc8c12ff12d89be09cd47231c7925e3b8afada9d50d"}, + {file = "pandas-1.4.3-cp38-cp38-win_amd64.whl", hash = "sha256:605d572126eb4ab2eadf5c59d5d69f0608df2bf7bcad5c5880a47a20a0699e3e"}, + {file = "pandas-1.4.3-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:a3924692160e3d847e18702bb048dc38e0e13411d2b503fecb1adf0fcf950ba4"}, + {file = "pandas-1.4.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:07238a58d7cbc8a004855ade7b75bbd22c0db4b0ffccc721556bab8a095515f6"}, + {file = "pandas-1.4.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:755679c49460bd0d2f837ab99f0a26948e68fa0718b7e42afbabd074d945bf84"}, + {file = "pandas-1.4.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:41fc406e374590a3d492325b889a2686b31e7a7780bec83db2512988550dadbf"}, + {file = "pandas-1.4.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1d9382f72a4f0e93909feece6fef5500e838ce1c355a581b3d8f259839f2ea76"}, + {file = "pandas-1.4.3-cp39-cp39-win32.whl", hash = "sha256:0daf876dba6c622154b2e6741f29e87161f844e64f84801554f879d27ba63c0d"}, + {file = "pandas-1.4.3-cp39-cp39-win_amd64.whl", hash = "sha256:721a3dd2f06ef942f83a819c0f3f6a648b2830b191a72bbe9451bcd49c3bd42e"}, + {file = "pandas-1.4.3.tar.gz", hash = "sha256:2ff7788468e75917574f080cd4681b27e1a7bf36461fe968b49a87b5a54d007c"}, +] +pandas-profiling = [ + {file = "pandas-profiling-3.2.0.tar.gz", hash = "sha256:8897c2628e3a9b5c915d038aa1782be7fa9e322e405a51aa4586466ab84e8461"}, + {file = "pandas_profiling-3.2.0-py2.py3-none-any.whl", hash = "sha256:dd063f35a99f8f0c75f109a2bbfa13a58209424b84b67842139562630d9035f7"}, +] +parsedatetime = [ + {file = "parsedatetime-2.4-py2-none-any.whl", hash = "sha256:9ee3529454bf35c40a77115f5a596771e59e1aee8c53306f346c461b8e913094"}, + {file = "parsedatetime-2.4.tar.gz", hash = "sha256:3d817c58fb9570d1eec1dd46fa9448cd644eeed4fb612684b02dfda3a79cb84b"}, +] +phik = [ + {file = "phik-0.12.2-cp310-cp310-macosx_10_13_x86_64.whl", hash = "sha256:c4acf6422b844c4cdc8b3c4a3b760838c7d5e0d28570b3ebeb8c95252b757ad3"}, + {file = "phik-0.12.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:404eda7c7e97540370948b05123c003f48de97a52bcc5429937c1aaea93b4cff"}, + {file = "phik-0.12.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:22de83d3a6321d9778a66d72438297913d3caae06581aa52272c871be99ac0af"}, + {file = "phik-0.12.2-cp310-cp310-win_amd64.whl", hash = "sha256:e1ade3e331c5ac66c2feb0fbb82405f76558426eb421d96212cedc1984f6593c"}, + {file = "phik-0.12.2-cp37-cp37m-macosx_10_13_x86_64.whl", hash = "sha256:6cce232b920b105e9ea6cbb1757d6e128d4c3ba5f8308b145b7422bcf6f965ab"}, + {file = "phik-0.12.2-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a77715fdccb798c7062e8ac5cf33fe0d758fd508b27ed7606df9daf81cd7776d"}, + {file = "phik-0.12.2-cp37-cp37m-win_amd64.whl", hash = "sha256:f4205e4bf832bad9e6a3cef47d76a30eff8c0aa38432366c31be38c6669d49cd"}, + {file = "phik-0.12.2-cp38-cp38-macosx_10_13_x86_64.whl", hash = "sha256:db501972688734bd26137050b863ab40fa43ee6deb220cebd364385a25b71ff3"}, + {file = "phik-0.12.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:9035af804d581b1e475b84a3d31ba2673bfde760500ebaeea9b3675aad5633df"}, + {file = "phik-0.12.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:087a691b05ea08dfd883689433cd46dfbd5b58c1aa9af782c24386d024dbde0e"}, + {file = "phik-0.12.2-cp38-cp38-win_amd64.whl", hash = "sha256:5d4a0545787a76b3fba27d8d84addafde4254f7c341a3115646e5b21550f72a4"}, + {file = "phik-0.12.2-cp39-cp39-macosx_10_13_x86_64.whl", hash = "sha256:e7efe7be5a8ec8c6be6a9416af2255e6cf38caf7b8226d2e79bb92540ffe2063"}, + {file = "phik-0.12.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:ec225cbb551c9bfa32b28740c99e35486574c61c1af576cf682bb2aebaf2755f"}, + {file = "phik-0.12.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1168c6230bb68be5fa2dad63f4a89dc11c7de1980d386d4f5c474978d1025303"}, + {file = "phik-0.12.2-cp39-cp39-win_amd64.whl", hash = "sha256:4426e8620090b9f69bf89e2adb3d70849fecfb899b9aeab8d8c39a8d1302d880"}, + {file = "phik-0.12.2.tar.gz", hash = "sha256:b0674eb829d23290433f718d2360122be804b170ccc807ad9d9a8d1c13985533"}, +] +pillow = [ + {file = "Pillow-9.2.0-cp310-cp310-macosx_10_10_x86_64.whl", hash = "sha256:a9c9bc489f8ab30906d7a85afac4b4944a572a7432e00698a7239f44a44e6efb"}, + {file = "Pillow-9.2.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:510cef4a3f401c246cfd8227b300828715dd055463cdca6176c2e4036df8bd4f"}, + {file = "Pillow-9.2.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7888310f6214f19ab2b6df90f3f06afa3df7ef7355fc025e78a3044737fab1f5"}, + {file = "Pillow-9.2.0-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:831e648102c82f152e14c1a0938689dbb22480c548c8d4b8b248b3e50967b88c"}, + {file = "Pillow-9.2.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1cc1d2451e8a3b4bfdb9caf745b58e6c7a77d2e469159b0d527a4554d73694d1"}, + {file = "Pillow-9.2.0-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:136659638f61a251e8ed3b331fc6ccd124590eeff539de57c5f80ef3a9594e58"}, + {file = "Pillow-9.2.0-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:6e8c66f70fb539301e064f6478d7453e820d8a2c631da948a23384865cd95544"}, + {file = "Pillow-9.2.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:37ff6b522a26d0538b753f0b4e8e164fdada12db6c6f00f62145d732d8a3152e"}, + {file = "Pillow-9.2.0-cp310-cp310-win32.whl", hash = "sha256:c79698d4cd9318d9481d89a77e2d3fcaeff5486be641e60a4b49f3d2ecca4e28"}, + {file = "Pillow-9.2.0-cp310-cp310-win_amd64.whl", hash = "sha256:254164c57bab4b459f14c64e93df11eff5ded575192c294a0c49270f22c5d93d"}, + {file = "Pillow-9.2.0-cp311-cp311-macosx_10_10_universal2.whl", hash = "sha256:408673ed75594933714482501fe97e055a42996087eeca7e5d06e33218d05aa8"}, + {file = "Pillow-9.2.0-cp311-cp311-macosx_11_0_universal2.whl", hash = "sha256:727dd1389bc5cb9827cbd1f9d40d2c2a1a0c9b32dd2261db522d22a604a6eec9"}, + {file = "Pillow-9.2.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:50dff9cc21826d2977ef2d2a205504034e3a4563ca6f5db739b0d1026658e004"}, + {file = "Pillow-9.2.0-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cb6259196a589123d755380b65127ddc60f4c64b21fc3bb46ce3a6ea663659b0"}, + {file = "Pillow-9.2.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7b0554af24df2bf96618dac71ddada02420f946be943b181108cac55a7a2dcd4"}, + {file = "Pillow-9.2.0-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:15928f824870535c85dbf949c09d6ae7d3d6ac2d6efec80f3227f73eefba741c"}, + {file = "Pillow-9.2.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:bdd0de2d64688ecae88dd8935012c4a72681e5df632af903a1dca8c5e7aa871a"}, + {file = "Pillow-9.2.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:d5b87da55a08acb586bad5c3aa3b86505f559b84f39035b233d5bf844b0834b1"}, + {file = "Pillow-9.2.0-cp311-cp311-win32.whl", hash = "sha256:b6d5e92df2b77665e07ddb2e4dbd6d644b78e4c0d2e9272a852627cdba0d75cf"}, + {file = "Pillow-9.2.0-cp311-cp311-win_amd64.whl", hash = "sha256:6bf088c1ce160f50ea40764f825ec9b72ed9da25346216b91361eef8ad1b8f8c"}, + {file = "Pillow-9.2.0-cp37-cp37m-macosx_10_10_x86_64.whl", hash = "sha256:2c58b24e3a63efd22554c676d81b0e57f80e0a7d3a5874a7e14ce90ec40d3069"}, + {file = "Pillow-9.2.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:eef7592281f7c174d3d6cbfbb7ee5984a671fcd77e3fc78e973d492e9bf0eb3f"}, + {file = "Pillow-9.2.0-cp37-cp37m-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:dcd7b9c7139dc8258d164b55696ecd16c04607f1cc33ba7af86613881ffe4ac8"}, + {file = "Pillow-9.2.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a138441e95562b3c078746a22f8fca8ff1c22c014f856278bdbdd89ca36cff1b"}, + {file = "Pillow-9.2.0-cp37-cp37m-manylinux_2_28_aarch64.whl", hash = "sha256:93689632949aff41199090eff5474f3990b6823404e45d66a5d44304e9cdc467"}, + {file = "Pillow-9.2.0-cp37-cp37m-manylinux_2_28_x86_64.whl", hash = "sha256:f3fac744f9b540148fa7715a435d2283b71f68bfb6d4aae24482a890aed18b59"}, + {file = "Pillow-9.2.0-cp37-cp37m-win32.whl", hash = "sha256:fa768eff5f9f958270b081bb33581b4b569faabf8774726b283edb06617101dc"}, + {file = "Pillow-9.2.0-cp37-cp37m-win_amd64.whl", hash = "sha256:69bd1a15d7ba3694631e00df8de65a8cb031911ca11f44929c97fe05eb9b6c1d"}, + {file = "Pillow-9.2.0-cp38-cp38-macosx_10_10_x86_64.whl", hash = "sha256:030e3460861488e249731c3e7ab59b07c7853838ff3b8e16aac9561bb345da14"}, + {file = "Pillow-9.2.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:74a04183e6e64930b667d321524e3c5361094bb4af9083db5c301db64cd341f3"}, + {file = "Pillow-9.2.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2d33a11f601213dcd5718109c09a52c2a1c893e7461f0be2d6febc2879ec2402"}, + {file = "Pillow-9.2.0-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1fd6f5e3c0e4697fa7eb45b6e93996299f3feee73a3175fa451f49a74d092b9f"}, + {file = "Pillow-9.2.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a647c0d4478b995c5e54615a2e5360ccedd2f85e70ab57fbe817ca613d5e63b8"}, + {file = "Pillow-9.2.0-cp38-cp38-manylinux_2_28_aarch64.whl", hash = "sha256:4134d3f1ba5f15027ff5c04296f13328fecd46921424084516bdb1b2548e66ff"}, + {file = "Pillow-9.2.0-cp38-cp38-manylinux_2_28_x86_64.whl", hash = "sha256:bc431b065722a5ad1dfb4df354fb9333b7a582a5ee39a90e6ffff688d72f27a1"}, + {file = "Pillow-9.2.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:1536ad017a9f789430fb6b8be8bf99d2f214c76502becc196c6f2d9a75b01b76"}, + {file = "Pillow-9.2.0-cp38-cp38-win32.whl", hash = "sha256:2ad0d4df0f5ef2247e27fc790d5c9b5a0af8ade9ba340db4a73bb1a4a3e5fb4f"}, + {file = "Pillow-9.2.0-cp38-cp38-win_amd64.whl", hash = "sha256:ec52c351b35ca269cb1f8069d610fc45c5bd38c3e91f9ab4cbbf0aebc136d9c8"}, + {file = "Pillow-9.2.0-cp39-cp39-macosx_10_10_x86_64.whl", hash = "sha256:0ed2c4ef2451de908c90436d6e8092e13a43992f1860275b4d8082667fbb2ffc"}, + {file = "Pillow-9.2.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:4ad2f835e0ad81d1689f1b7e3fbac7b01bb8777d5a985c8962bedee0cc6d43da"}, + {file = "Pillow-9.2.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ea98f633d45f7e815db648fd7ff0f19e328302ac36427343e4432c84432e7ff4"}, + {file = "Pillow-9.2.0-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7761afe0126d046974a01e030ae7529ed0ca6a196de3ec6937c11df0df1bc91c"}, + {file = "Pillow-9.2.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9a54614049a18a2d6fe156e68e188da02a046a4a93cf24f373bffd977e943421"}, + {file = "Pillow-9.2.0-cp39-cp39-manylinux_2_28_aarch64.whl", hash = "sha256:5aed7dde98403cd91d86a1115c78d8145c83078e864c1de1064f52e6feb61b20"}, + {file = "Pillow-9.2.0-cp39-cp39-manylinux_2_28_x86_64.whl", hash = "sha256:13b725463f32df1bfeacbf3dd197fb358ae8ebcd8c5548faa75126ea425ccb60"}, + {file = "Pillow-9.2.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:808add66ea764ed97d44dda1ac4f2cfec4c1867d9efb16a33d158be79f32b8a4"}, + {file = "Pillow-9.2.0-cp39-cp39-win32.whl", hash = "sha256:337a74fd2f291c607d220c793a8135273c4c2ab001b03e601c36766005f36885"}, + {file = "Pillow-9.2.0-cp39-cp39-win_amd64.whl", hash = "sha256:fac2d65901fb0fdf20363fbd345c01958a742f2dc62a8dd4495af66e3ff502a4"}, + {file = "Pillow-9.2.0-pp37-pypy37_pp73-macosx_10_10_x86_64.whl", hash = "sha256:ad2277b185ebce47a63f4dc6302e30f05762b688f8dc3de55dbae4651872cdf3"}, + {file = "Pillow-9.2.0-pp37-pypy37_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7c7b502bc34f6e32ba022b4a209638f9e097d7a9098104ae420eb8186217ebbb"}, + {file = "Pillow-9.2.0-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3d1f14f5f691f55e1b47f824ca4fdcb4b19b4323fe43cc7bb105988cad7496be"}, + {file = "Pillow-9.2.0-pp37-pypy37_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:dfe4c1fedfde4e2fbc009d5ad420647f7730d719786388b7de0999bf32c0d9fd"}, + {file = "Pillow-9.2.0-pp38-pypy38_pp73-macosx_10_10_x86_64.whl", hash = "sha256:f07f1f00e22b231dd3d9b9208692042e29792d6bd4f6639415d2f23158a80013"}, + {file = "Pillow-9.2.0-pp38-pypy38_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1802f34298f5ba11d55e5bb09c31997dc0c6aed919658dfdf0198a2fe75d5490"}, + {file = "Pillow-9.2.0-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:17d4cafe22f050b46d983b71c707162d63d796a1235cdf8b9d7a112e97b15bac"}, + {file = "Pillow-9.2.0-pp38-pypy38_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:96b5e6874431df16aee0c1ba237574cb6dff1dcb173798faa6a9d8b399a05d0e"}, + {file = "Pillow-9.2.0-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:0030fdbd926fb85844b8b92e2f9449ba89607231d3dd597a21ae72dc7fe26927"}, + {file = "Pillow-9.2.0.tar.gz", hash = "sha256:75e636fd3e0fb872693f23ccb8a5ff2cd578801251f3a4f6854c6a5d437d3c04"}, +] +pluggy = [ + {file = "pluggy-1.0.0-py2.py3-none-any.whl", hash = "sha256:74134bbf457f031a36d68416e1509f34bd5ccc019f0bcc952c7b909d06b37bd3"}, + {file = "pluggy-1.0.0.tar.gz", hash = "sha256:4224373bacce55f955a878bf9cfa763c1e360858e330072059e10bad68531159"}, +] +priority = [ + {file = "priority-2.0.0-py3-none-any.whl", hash = "sha256:6f8eefce5f3ad59baf2c080a664037bb4725cd0a790d53d59ab4059288faf6aa"}, + {file = "priority-2.0.0.tar.gz", hash = "sha256:c965d54f1b8d0d0b19479db3924c7c36cf672dbf2aec92d43fbdaf4492ba18c0"}, +] +py = [ + {file = "py-1.11.0-py2.py3-none-any.whl", hash = "sha256:607c53218732647dff4acdfcd50cb62615cedf612e72d1724fb1a0cc6405b378"}, + {file = "py-1.11.0.tar.gz", hash = "sha256:51c75c4126074b472f746a24399ad32f6053d1b34b68d2fa41e558e6f4a98719"}, +] +pycodestyle = [ + {file = "pycodestyle-2.9.1-py2.py3-none-any.whl", hash = "sha256:d1735fc58b418fd7c5f658d28d943854f8a849b01a5d0a1e6f3f3fdd0166804b"}, + {file = "pycodestyle-2.9.1.tar.gz", hash = "sha256:2c9607871d58c76354b697b42f5d57e1ada7d261c261efac224b664affdc5785"}, +] +pydantic = [ + {file = "pydantic-1.9.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:9c9e04a6cdb7a363d7cb3ccf0efea51e0abb48e180c0d31dca8d247967d85c6e"}, + {file = "pydantic-1.9.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:fafe841be1103f340a24977f61dee76172e4ae5f647ab9e7fd1e1fca51524f08"}, + {file = "pydantic-1.9.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:afacf6d2a41ed91fc631bade88b1d319c51ab5418870802cedb590b709c5ae3c"}, + {file = "pydantic-1.9.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3ee0d69b2a5b341fc7927e92cae7ddcfd95e624dfc4870b32a85568bd65e6131"}, + {file = "pydantic-1.9.2-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:ff68fc85355532ea77559ede81f35fff79a6a5543477e168ab3a381887caea76"}, + {file = "pydantic-1.9.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:c0f5e142ef8217019e3eef6ae1b6b55f09a7a15972958d44fbd228214cede567"}, + {file = "pydantic-1.9.2-cp310-cp310-win_amd64.whl", hash = "sha256:615661bfc37e82ac677543704437ff737418e4ea04bef9cf11c6d27346606044"}, + {file = "pydantic-1.9.2-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:328558c9f2eed77bd8fffad3cef39dbbe3edc7044517f4625a769d45d4cf7555"}, + {file = "pydantic-1.9.2-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2bd446bdb7755c3a94e56d7bdfd3ee92396070efa8ef3a34fab9579fe6aa1d84"}, + {file = "pydantic-1.9.2-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e0b214e57623a535936005797567231a12d0da0c29711eb3514bc2b3cd008d0f"}, + {file = "pydantic-1.9.2-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:d8ce3fb0841763a89322ea0432f1f59a2d3feae07a63ea2c958b2315e1ae8adb"}, + {file = "pydantic-1.9.2-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:b34ba24f3e2d0b39b43f0ca62008f7ba962cff51efa56e64ee25c4af6eed987b"}, + {file = "pydantic-1.9.2-cp36-cp36m-win_amd64.whl", hash = "sha256:84d76ecc908d917f4684b354a39fd885d69dd0491be175f3465fe4b59811c001"}, + {file = "pydantic-1.9.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:4de71c718c9756d679420c69f216776c2e977459f77e8f679a4a961dc7304a56"}, + {file = "pydantic-1.9.2-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5803ad846cdd1ed0d97eb00292b870c29c1f03732a010e66908ff48a762f20e4"}, + {file = "pydantic-1.9.2-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a8c5360a0297a713b4123608a7909e6869e1b56d0e96eb0d792c27585d40757f"}, + {file = "pydantic-1.9.2-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:cdb4272678db803ddf94caa4f94f8672e9a46bae4a44f167095e4d06fec12979"}, + {file = "pydantic-1.9.2-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:19b5686387ea0d1ea52ecc4cffb71abb21702c5e5b2ac626fd4dbaa0834aa49d"}, + {file = "pydantic-1.9.2-cp37-cp37m-win_amd64.whl", hash = "sha256:32e0b4fb13ad4db4058a7c3c80e2569adbd810c25e6ca3bbd8b2a9cc2cc871d7"}, + {file = "pydantic-1.9.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:91089b2e281713f3893cd01d8e576771cd5bfdfbff5d0ed95969f47ef6d676c3"}, + {file = "pydantic-1.9.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:e631c70c9280e3129f071635b81207cad85e6c08e253539467e4ead0e5b219aa"}, + {file = "pydantic-1.9.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4b3946f87e5cef3ba2e7bd3a4eb5a20385fe36521d6cc1ebf3c08a6697c6cfb3"}, + {file = "pydantic-1.9.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5565a49effe38d51882cb7bac18bda013cdb34d80ac336428e8908f0b72499b0"}, + {file = "pydantic-1.9.2-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:bd67cb2c2d9602ad159389c29e4ca964b86fa2f35c2faef54c3eb28b4efd36c8"}, + {file = "pydantic-1.9.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:4aafd4e55e8ad5bd1b19572ea2df546ccace7945853832bb99422a79c70ce9b8"}, + {file = "pydantic-1.9.2-cp38-cp38-win_amd64.whl", hash = "sha256:d70916235d478404a3fa8c997b003b5f33aeac4686ac1baa767234a0f8ac2326"}, + {file = "pydantic-1.9.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:f0ca86b525264daa5f6b192f216a0d1e860b7383e3da1c65a1908f9c02f42801"}, + {file = "pydantic-1.9.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:1061c6ee6204f4f5a27133126854948e3b3d51fcc16ead2e5d04378c199b2f44"}, + {file = "pydantic-1.9.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e78578f0c7481c850d1c969aca9a65405887003484d24f6110458fb02cca7747"}, + {file = "pydantic-1.9.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5da164119602212a3fe7e3bc08911a89db4710ae51444b4224c2382fd09ad453"}, + {file = "pydantic-1.9.2-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:7ead3cd020d526f75b4188e0a8d71c0dbbe1b4b6b5dc0ea775a93aca16256aeb"}, + {file = "pydantic-1.9.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:7d0f183b305629765910eaad707800d2f47c6ac5bcfb8c6397abdc30b69eeb15"}, + {file = "pydantic-1.9.2-cp39-cp39-win_amd64.whl", hash = "sha256:f1a68f4f65a9ee64b6ccccb5bf7e17db07caebd2730109cb8a95863cfa9c4e55"}, + {file = "pydantic-1.9.2-py3-none-any.whl", hash = "sha256:78a4d6bdfd116a559aeec9a4cfe77dda62acc6233f8b56a716edad2651023e5e"}, + {file = "pydantic-1.9.2.tar.gz", hash = "sha256:8cb0bc509bfb71305d7a59d00163d5f9fc4530f0881ea32c74ff4f74c85f3d3d"}, +] +pyflakes = [ + {file = "pyflakes-2.5.0-py2.py3-none-any.whl", hash = "sha256:4579f67d887f804e67edb544428f264b7b24f435b263c4614f384135cea553d2"}, + {file = "pyflakes-2.5.0.tar.gz", hash = "sha256:491feb020dca48ccc562a8c0cbe8df07ee13078df59813b83959cbdada312ea3"}, +] +pyparsing = [ + {file = "pyparsing-3.0.9-py3-none-any.whl", hash = "sha256:5026bae9a10eeaefb61dab2f09052b9f4307d44aee4eda64b309723d8d206bbc"}, + {file = "pyparsing-3.0.9.tar.gz", hash = "sha256:2b020ecf7d21b687f219b71ecad3631f644a47f01403fa1d1036b0c6416d70fb"}, +] +pytest = [ + {file = "pytest-7.1.2-py3-none-any.whl", hash = "sha256:13d0e3ccfc2b6e26be000cb6568c832ba67ba32e719443bfe725814d3c42433c"}, + {file = "pytest-7.1.2.tar.gz", hash = "sha256:a06a0425453864a270bc45e71f783330a7428defb4230fb5e6a731fde06ecd45"}, +] +pytest-asyncio = [ + {file = "pytest-asyncio-0.19.0.tar.gz", hash = "sha256:ac4ebf3b6207259750bc32f4c1d8fcd7e79739edbc67ad0c58dd150b1d072fed"}, + {file = "pytest_asyncio-0.19.0-py3-none-any.whl", hash = "sha256:7a97e37cfe1ed296e2e84941384bdd37c376453912d397ed39293e0916f521fa"}, +] +pytest-cov = [ + {file = "pytest-cov-2.5.1.tar.gz", hash = "sha256:03aa752cf11db41d281ea1d807d954c4eda35cfa1b21d6971966cc041bbf6e2d"}, + {file = "pytest_cov-2.5.1-py2.py3-none-any.whl", hash = "sha256:890fe5565400902b0c78b5357004aab1c814115894f4f21370e2433256a3eeec"}, +] +python-dateutil = [ + {file = "python-dateutil-2.8.2.tar.gz", hash = "sha256:0123cacc1627ae19ddf3c27a5de5bd67ee4586fbdd6440d9748f8abb483d3e86"}, + {file = "python_dateutil-2.8.2-py2.py3-none-any.whl", hash = "sha256:961d03dc3453ebbc59dbdea9e4e11c5651520a876d0f4db161e8674aae935da9"}, +] +python-slugify = [ + {file = "python-slugify-6.1.2.tar.gz", hash = "sha256:272d106cb31ab99b3496ba085e3fea0e9e76dcde967b5e9992500d1f785ce4e1"}, + {file = "python_slugify-6.1.2-py2.py3-none-any.whl", hash = "sha256:7b2c274c308b62f4269a9ba701aa69a797e9bca41aeee5b3a9e79e36b6656927"}, +] +python-stdnum = [ + {file = "python-stdnum-1.15.tar.gz", hash = "sha256:ff0e4d2b8731711cf2a73d22964139208088b4bd1a3e3b0c4f78d6e823dd8923"}, + {file = "python_stdnum-1.15-py2.py3-none-any.whl", hash = "sha256:46c05dff2d5ff89b12cc63ab54cca1e4788e4e3087c163a1b7434a53fa926f28"}, +] +pytimeparse = [ + {file = "pytimeparse-1.1.8-py2.py3-none-any.whl", hash = "sha256:04b7be6cc8bd9f5647a6325444926c3ac34ee6bc7e69da4367ba282f076036bd"}, + {file = "pytimeparse-1.1.8.tar.gz", hash = "sha256:e86136477be924d7e670646a98561957e8ca7308d44841e21f5ddea757556a0a"}, +] +pytz = [ + {file = "pytz-2022.2.1-py2.py3-none-any.whl", hash = "sha256:220f481bdafa09c3955dfbdddb7b57780e9a94f5127e35456a48589b9e0c0197"}, + {file = "pytz-2022.2.1.tar.gz", hash = "sha256:cea221417204f2d1a2aa03ddae3e867921971d0d76f14d87abb4414415bbdcf5"}, +] +pywavelets = [ + {file = "PyWavelets-1.3.0-cp310-cp310-macosx_10_13_universal2.whl", hash = "sha256:eebaa9c28600da336743fefd650332460c132792660e70eb09abf343b0664b87"}, + {file = "PyWavelets-1.3.0-cp310-cp310-macosx_10_13_x86_64.whl", hash = "sha256:3eeffcf2f7eebae5cc27cb11a7d0d96118e2e9f75ac38ff1a05373d5fe75accb"}, + {file = "PyWavelets-1.3.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:35a945bea9da6db9755e42e06e871846514ae91bde3ae24a08a1d090b003a23b"}, + {file = "PyWavelets-1.3.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e8876764e349673ee8d48bc3cd0afd2f9f7b65378998e2665af12c277c8a56de"}, + {file = "PyWavelets-1.3.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c98ac1cee6276db05768e450dc3002033be6c2819c906103a974e0fb0d436f41"}, + {file = "PyWavelets-1.3.0-cp310-cp310-win32.whl", hash = "sha256:6ecfe051ccb097c2dcdcb0977e0a684e76144d6694a202badf0780143d8536f0"}, + {file = "PyWavelets-1.3.0-cp310-cp310-win_amd64.whl", hash = "sha256:437806465cfa5f2d91809ec13154be050b84a11025784a6b6ce04ac452872b36"}, + {file = "PyWavelets-1.3.0-cp37-cp37m-macosx_10_13_x86_64.whl", hash = "sha256:3c4ebe7ff2c9092f6bdd1f8bf98ce2745f5d43a9936d6e342ee83fbcae548116"}, + {file = "PyWavelets-1.3.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d4f9ed4f175c66c9b8646a93fd54c588fd8f4b2517f53c59aea5cdf370f9c9ba"}, + {file = "PyWavelets-1.3.0-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:41e4f0a3a6a088e955006513fe72f863cea3ce293033131cacb8a1a3068ed228"}, + {file = "PyWavelets-1.3.0-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:5b76731d2077242611b32f2e11c72adbf126b432ceae92e2ce8d0f693974c96d"}, + {file = "PyWavelets-1.3.0-cp37-cp37m-win32.whl", hash = "sha256:3d3ecc2ee87be94fb2dc8c2d35bcae3f24708677196e80028d24ba0fd2f6a70a"}, + {file = "PyWavelets-1.3.0-cp37-cp37m-win_amd64.whl", hash = "sha256:91e1b220f0ddd4c127bab718363c2c4a07dbcd95b9c4bfed09a3cdae47dbba43"}, + {file = "PyWavelets-1.3.0-cp38-cp38-macosx_10_13_universal2.whl", hash = "sha256:8a5941d1f4eb1bc9569c655b63ecb31aa15b3ef0fc9b57df275892c39bccc59e"}, + {file = "PyWavelets-1.3.0-cp38-cp38-macosx_10_13_x86_64.whl", hash = "sha256:a555a7a85da01357d8258cb45f751881f69013f8920f8738718c60cf8a47b755"}, + {file = "PyWavelets-1.3.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:69e9a46facf89b51e5700d10f6d831f29745471c1ab42917f2f849a257b9fd77"}, + {file = "PyWavelets-1.3.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a51225d24811ba7ef5184c03bb7072db0aa9651c4370a115d4069dedfb8d2f7a"}, + {file = "PyWavelets-1.3.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d7369597e1b1d125eb4b458a36cef052beed188444e55ed21445c1196008e200"}, + {file = "PyWavelets-1.3.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:307ab8a4c3e5c2b8f7d3d371de4a5f019cf4b030b897c3394a4a7ad157369367"}, + {file = "PyWavelets-1.3.0-cp38-cp38-win32.whl", hash = "sha256:27e99818d3c26481de3c68dbe880a7fcafe661cc031b22eff4a64237fe17a7ff"}, + {file = "PyWavelets-1.3.0-cp38-cp38-win_amd64.whl", hash = "sha256:3383d106fa8da0c2df30401ad056cd7a11b76d885f4bfa16ca7bcc6b4ca2831c"}, + {file = "PyWavelets-1.3.0-cp39-cp39-macosx_10_13_universal2.whl", hash = "sha256:84c58a179bdb9fc71039b1f68bcd0718a7d9814b5e3741d7681d3e027bb81b52"}, + {file = "PyWavelets-1.3.0-cp39-cp39-macosx_10_13_x86_64.whl", hash = "sha256:fccf468c55427828a3c534b651311f2759210836491c1112e1548e1babe368a5"}, + {file = "PyWavelets-1.3.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:0ed3afbda88498b3ea3c861bf5b55e4feca41747730a71a22102ed5a74d1e453"}, + {file = "PyWavelets-1.3.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:38cc635c08a050e175a492e66c9b63a8e1f42254e6879e614b6c9d8d69e0887f"}, + {file = "PyWavelets-1.3.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a486160f83efd8517cd748796adbab7c445ee8a3e1d168b4b8b60ed0f5aee3a0"}, + {file = "PyWavelets-1.3.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:f6e7d969a6ef64ae8be1766b0b0e32debb13424543d331911b8d7e967d60dd42"}, + {file = "PyWavelets-1.3.0-cp39-cp39-win32.whl", hash = "sha256:de67deb275474094e160900ab7e07f2a721b9cd351cf3826c4a3ab89bb71d4b3"}, + {file = "PyWavelets-1.3.0-cp39-cp39-win_amd64.whl", hash = "sha256:a354979e2ee8cd71a8952ded381f3d9f981692b73c6842bcc6c9f64047e0a5be"}, + {file = "PyWavelets-1.3.0.tar.gz", hash = "sha256:cbaa9d62052d9daf8da765fc8e7c30c38ea2b8e9e1c18841913dfb4aec671ee5"}, +] +pyyaml = [ + {file = "PyYAML-6.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d4db7c7aef085872ef65a8fd7d6d09a14ae91f691dec3e87ee5ee0539d516f53"}, + {file = "PyYAML-6.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9df7ed3b3d2e0ecfe09e14741b857df43adb5a3ddadc919a2d94fbdf78fea53c"}, + {file = "PyYAML-6.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:77f396e6ef4c73fdc33a9157446466f1cff553d979bd00ecb64385760c6babdc"}, + {file = "PyYAML-6.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a80a78046a72361de73f8f395f1f1e49f956c6be882eed58505a15f3e430962b"}, + {file = "PyYAML-6.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:f84fbc98b019fef2ee9a1cb3ce93e3187a6df0b2538a651bfb890254ba9f90b5"}, + {file = "PyYAML-6.0-cp310-cp310-win32.whl", hash = "sha256:2cd5df3de48857ed0544b34e2d40e9fac445930039f3cfe4bcc592a1f836d513"}, + {file = "PyYAML-6.0-cp310-cp310-win_amd64.whl", hash = "sha256:daf496c58a8c52083df09b80c860005194014c3698698d1a57cbcfa182142a3a"}, + {file = "PyYAML-6.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:897b80890765f037df3403d22bab41627ca8811ae55e9a722fd0392850ec4d86"}, + {file = "PyYAML-6.0-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:50602afada6d6cbfad699b0c7bb50d5ccffa7e46a3d738092afddc1f9758427f"}, + {file = "PyYAML-6.0-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:48c346915c114f5fdb3ead70312bd042a953a8ce5c7106d5bfb1a5254e47da92"}, + {file = "PyYAML-6.0-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:98c4d36e99714e55cfbaaee6dd5badbc9a1ec339ebfc3b1f52e293aee6bb71a4"}, + {file = "PyYAML-6.0-cp36-cp36m-win32.whl", hash = "sha256:0283c35a6a9fbf047493e3a0ce8d79ef5030852c51e9d911a27badfde0605293"}, + {file = "PyYAML-6.0-cp36-cp36m-win_amd64.whl", hash = "sha256:07751360502caac1c067a8132d150cf3d61339af5691fe9e87803040dbc5db57"}, + {file = "PyYAML-6.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:819b3830a1543db06c4d4b865e70ded25be52a2e0631ccd2f6a47a2822f2fd7c"}, + {file = "PyYAML-6.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:473f9edb243cb1935ab5a084eb238d842fb8f404ed2193a915d1784b5a6b5fc0"}, + {file = "PyYAML-6.0-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0ce82d761c532fe4ec3f87fc45688bdd3a4c1dc5e0b4a19814b9009a29baefd4"}, + {file = "PyYAML-6.0-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:231710d57adfd809ef5d34183b8ed1eeae3f76459c18fb4a0b373ad56bedcdd9"}, + {file = "PyYAML-6.0-cp37-cp37m-win32.whl", hash = "sha256:c5687b8d43cf58545ade1fe3e055f70eac7a5a1a0bf42824308d868289a95737"}, + {file = "PyYAML-6.0-cp37-cp37m-win_amd64.whl", hash = "sha256:d15a181d1ecd0d4270dc32edb46f7cb7733c7c508857278d3d378d14d606db2d"}, + {file = "PyYAML-6.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:0b4624f379dab24d3725ffde76559cff63d9ec94e1736b556dacdfebe5ab6d4b"}, + {file = "PyYAML-6.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:213c60cd50106436cc818accf5baa1aba61c0189ff610f64f4a3e8c6726218ba"}, + {file = "PyYAML-6.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9fa600030013c4de8165339db93d182b9431076eb98eb40ee068700c9c813e34"}, + {file = "PyYAML-6.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:277a0ef2981ca40581a47093e9e2d13b3f1fbbeffae064c1d21bfceba2030287"}, + {file = "PyYAML-6.0-cp38-cp38-win32.whl", hash = "sha256:d4eccecf9adf6fbcc6861a38015c2a64f38b9d94838ac1810a9023a0609e1b78"}, + {file = "PyYAML-6.0-cp38-cp38-win_amd64.whl", hash = "sha256:1e4747bc279b4f613a09eb64bba2ba602d8a6664c6ce6396a4d0cd413a50ce07"}, + {file = "PyYAML-6.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:055d937d65826939cb044fc8c9b08889e8c743fdc6a32b33e2390f66013e449b"}, + {file = "PyYAML-6.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:e61ceaab6f49fb8bdfaa0f92c4b57bcfbea54c09277b1b4f7ac376bfb7a7c174"}, + {file = "PyYAML-6.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d67d839ede4ed1b28a4e8909735fc992a923cdb84e618544973d7dfc71540803"}, + {file = "PyYAML-6.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cba8c411ef271aa037d7357a2bc8f9ee8b58b9965831d9e51baf703280dc73d3"}, + {file = "PyYAML-6.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:40527857252b61eacd1d9af500c3337ba8deb8fc298940291486c465c8b46ec0"}, + {file = "PyYAML-6.0-cp39-cp39-win32.whl", hash = "sha256:b5b9eccad747aabaaffbc6064800670f0c297e52c12754eb1d976c57e4f74dcb"}, + {file = "PyYAML-6.0-cp39-cp39-win_amd64.whl", hash = "sha256:b3d267842bf12586ba6c734f89d1f5b871df0273157918b0ccefa29deb05c21c"}, + {file = "PyYAML-6.0.tar.gz", hash = "sha256:68fb519c14306fec9720a2a5b45bc9f0c8d1b9c72adf45c37baedfcd949c35a2"}, +] +quart = [ + {file = "Quart-0.14.1-py3-none-any.whl", hash = "sha256:7b13786e07541cc9ce1466fdc6a6ccd5f36eb39118edd25a42d617593cd17707"}, + {file = "Quart-0.14.1.tar.gz", hash = "sha256:429c5b4ff27e1d2f9ca0aacc38f6aba0ff49b38b815448bf24b613d3de12ea02"}, +] +quart-cors = [ + {file = "Quart-CORS-0.3.0.tar.gz", hash = "sha256:c08bdb326219b6c186d19ed6a97a7fd02de8fe36c7856af889494c69b525c53c"}, + {file = "Quart_CORS-0.3.0-py3-none-any.whl", hash = "sha256:020a17d504264db86cada3c1335ef174af28b33f57cee321ddc46d69c33d5c8e"}, +] +requests = [ + {file = "requests-2.27.1-py2.py3-none-any.whl", hash = "sha256:f22fa1e554c9ddfd16e6e41ac79759e17be9e492b3587efa038054674760e72d"}, + {file = "requests-2.27.1.tar.gz", hash = "sha256:68d7c56fd5a8999887728ef304a6d12edc7be74f1cfa47714fc8b414525c9a61"}, +] +s3transfer = [ + {file = "s3transfer-0.5.2-py3-none-any.whl", hash = "sha256:7a6f4c4d1fdb9a2b640244008e142cbc2cd3ae34b386584ef044dd0f27101971"}, + {file = "s3transfer-0.5.2.tar.gz", hash = "sha256:95c58c194ce657a5f4fb0b9e60a84968c808888aed628cd98ab8771fe1db98ed"}, +] +scipy = [ + {file = "scipy-1.6.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:a15a1f3fc0abff33e792d6049161b7795909b40b97c6cc2934ed54384017ab76"}, + {file = "scipy-1.6.1-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:e79570979ccdc3d165456dd62041d9556fb9733b86b4b6d818af7a0afc15f092"}, + {file = "scipy-1.6.1-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:a423533c55fec61456dedee7b6ee7dce0bb6bfa395424ea374d25afa262be261"}, + {file = "scipy-1.6.1-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:33d6b7df40d197bdd3049d64e8e680227151673465e5d85723b3b8f6b15a6ced"}, + {file = "scipy-1.6.1-cp37-cp37m-win32.whl", hash = "sha256:6725e3fbb47da428794f243864f2297462e9ee448297c93ed1dcbc44335feb78"}, + {file = "scipy-1.6.1-cp37-cp37m-win_amd64.whl", hash = "sha256:5fa9c6530b1661f1370bcd332a1e62ca7881785cc0f80c0d559b636567fab63c"}, + {file = "scipy-1.6.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:bd50daf727f7c195e26f27467c85ce653d41df4358a25b32434a50d8870fc519"}, + {file = "scipy-1.6.1-cp38-cp38-manylinux1_i686.whl", hash = "sha256:f46dd15335e8a320b0fb4685f58b7471702234cba8bb3442b69a3e1dc329c345"}, + {file = "scipy-1.6.1-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:0e5b0ccf63155d90da576edd2768b66fb276446c371b73841e3503be1d63fb5d"}, + {file = "scipy-1.6.1-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:2481efbb3740977e3c831edfd0bd9867be26387cacf24eb5e366a6a374d3d00d"}, + {file = "scipy-1.6.1-cp38-cp38-win32.whl", hash = "sha256:68cb4c424112cd4be886b4d979c5497fba190714085f46b8ae67a5e4416c32b4"}, + {file = "scipy-1.6.1-cp38-cp38-win_amd64.whl", hash = "sha256:5f331eeed0297232d2e6eea51b54e8278ed8bb10b099f69c44e2558c090d06bf"}, + {file = "scipy-1.6.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:0c8a51d33556bf70367452d4d601d1742c0e806cd0194785914daf19775f0e67"}, + {file = "scipy-1.6.1-cp39-cp39-manylinux1_i686.whl", hash = "sha256:83bf7c16245c15bc58ee76c5418e46ea1811edcc2e2b03041b804e46084ab627"}, + {file = "scipy-1.6.1-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:794e768cc5f779736593046c9714e0f3a5940bc6dcc1dba885ad64cbfb28e9f0"}, + {file = "scipy-1.6.1-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:5da5471aed911fe7e52b86bf9ea32fb55ae93e2f0fac66c32e58897cfb02fa07"}, + {file = "scipy-1.6.1-cp39-cp39-win32.whl", hash = "sha256:8e403a337749ed40af60e537cc4d4c03febddcc56cd26e774c9b1b600a70d3e4"}, + {file = "scipy-1.6.1-cp39-cp39-win_amd64.whl", hash = "sha256:a5193a098ae9f29af283dcf0041f762601faf2e595c0db1da929875b7570353f"}, + {file = "scipy-1.6.1.tar.gz", hash = "sha256:c4fceb864890b6168e79b0e714c585dbe2fd4222768ee90bc1aa0f8218691b11"}, +] +seaborn = [ + {file = "seaborn-0.11.2-py3-none-any.whl", hash = "sha256:85a6baa9b55f81a0623abddc4a26b334653ff4c6b18c418361de19dbba0ef283"}, + {file = "seaborn-0.11.2.tar.gz", hash = "sha256:cf45e9286d40826864be0e3c066f98536982baf701a7caa386511792d61ff4f6"}, +] +sentry-sdk = [ + {file = "sentry-sdk-1.0.0.tar.gz", hash = "sha256:71de00c9711926816f750bc0f57ef2abbcb1bfbdf5378c601df7ec978f44857a"}, + {file = "sentry_sdk-1.0.0-py2.py3-none-any.whl", hash = "sha256:9221e985f425913204989d0e0e1cbb719e8b7fa10540f1bc509f660c06a34e66"}, +] +setuptools-scm = [ + {file = "setuptools_scm-6.4.2-py3-none-any.whl", hash = "sha256:acea13255093849de7ccb11af9e1fb8bde7067783450cee9ef7a93139bddf6d4"}, + {file = "setuptools_scm-6.4.2.tar.gz", hash = "sha256:6833ac65c6ed9711a4d5d2266f8024cfa07c533a0e55f4c12f6eff280a5a9e30"}, +] +six = [ + {file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"}, + {file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"}, +] +sqlalchemy = [ + {file = "SQLAlchemy-1.4.40-cp27-cp27m-macosx_10_14_x86_64.whl", hash = "sha256:b07fc38e6392a65935dc8b486229679142b2ea33c94059366b4d8b56f1e35a97"}, + {file = "SQLAlchemy-1.4.40-cp27-cp27m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:fb4edb6c354eac0fcc07cb91797e142f702532dbb16c1d62839d6eec35f814cf"}, + {file = "SQLAlchemy-1.4.40-cp27-cp27m-win32.whl", hash = "sha256:2026632051a93997cf8f6fda14360f99230be1725b7ab2ef15be205a4b8a5430"}, + {file = "SQLAlchemy-1.4.40-cp27-cp27m-win_amd64.whl", hash = "sha256:f2aa85aebc0ef6b342d5d3542f969caa8c6a63c8d36cf5098769158a9fa2123c"}, + {file = "SQLAlchemy-1.4.40-cp27-cp27mu-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:a0b9e3d81f86ba04007f0349e373a5b8c81ec2047aadb8d669caf8c54a092461"}, + {file = "SQLAlchemy-1.4.40-cp310-cp310-macosx_10_15_x86_64.whl", hash = "sha256:1ab08141d93de83559f6a7d9a962830f918623a885b3759ec2b9d1a531ff28fe"}, + {file = "SQLAlchemy-1.4.40-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:00dd998b43b282c71de46b061627b5edb9332510eb1edfc5017b9e4356ed44ea"}, + {file = "SQLAlchemy-1.4.40-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:bb342c0e25cc8f78a0e7c692da3b984f072666b316fbbec2a0e371cb4dfef5f0"}, + {file = "SQLAlchemy-1.4.40-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:23b693876ac7963b6bc7b1a5f3a2642f38d2624af834faad5933913928089d1b"}, + {file = "SQLAlchemy-1.4.40-cp310-cp310-win32.whl", hash = "sha256:2cf50611ef4221ad587fb7a1708e61ff72966f84330c6317642e08d6db4138fd"}, + {file = "SQLAlchemy-1.4.40-cp310-cp310-win_amd64.whl", hash = "sha256:26ee4dbac5dd7abf18bf3cd8f04e51f72c339caf702f68172d308888cd26c6c9"}, + {file = "SQLAlchemy-1.4.40-cp36-cp36m-macosx_10_14_x86_64.whl", hash = "sha256:b41b87b929118838bafc4bb18cf3c5cd1b3be4b61cd9042e75174df79e8ac7a2"}, + {file = "SQLAlchemy-1.4.40-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:885e11638946472b4a0a7db8e6df604b2cf64d23dc40eedc3806d869fcb18fae"}, + {file = "SQLAlchemy-1.4.40-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:b7ff0a8bf0aec1908b92b8dfa1246128bf4f94adbdd3da6730e9c542e112542d"}, + {file = "SQLAlchemy-1.4.40-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cfa8ab4ba0c97ab6bcae1f0948497d14c11b6c6ecd1b32b8a79546a0823d8211"}, + {file = "SQLAlchemy-1.4.40-cp36-cp36m-win32.whl", hash = "sha256:d259fa08e4b3ed952c01711268bcf6cd2442b0c54866d64aece122f83da77c6d"}, + {file = "SQLAlchemy-1.4.40-cp36-cp36m-win_amd64.whl", hash = "sha256:c8d974c991eef0cd29418a5957ae544559dc326685a6f26b3a914c87759bf2f4"}, + {file = "SQLAlchemy-1.4.40-cp37-cp37m-macosx_10_15_x86_64.whl", hash = "sha256:28b1791a30d62fc104070965f1a2866699c45bbf5adc0be0cf5f22935edcac58"}, + {file = "SQLAlchemy-1.4.40-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b7ccdca6cd167611f4a62a8c2c0c4285c2535640d77108f782ce3f3cccb70f3a"}, + {file = "SQLAlchemy-1.4.40-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:69deec3a94de10062080d91e1ba69595efeafeafe68b996426dec9720031fb25"}, + {file = "SQLAlchemy-1.4.40-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:63ad778f4e80913fb171247e4fa82123d0068615ae1d51a9791fc4284cb81748"}, + {file = "SQLAlchemy-1.4.40-cp37-cp37m-win32.whl", hash = "sha256:9ced2450c9fd016f9232d976661623e54c450679eeefc7aa48a3d29924a63189"}, + {file = "SQLAlchemy-1.4.40-cp37-cp37m-win_amd64.whl", hash = "sha256:cdee4d475e35684d210dc6b430ff8ca2ed0636378ac19b457e2f6f350d1f5acc"}, + {file = "SQLAlchemy-1.4.40-cp38-cp38-macosx_10_15_x86_64.whl", hash = "sha256:08b47c971327e733ffd6bae2d4f50a7b761793efe69d41067fcba86282819eea"}, + {file = "SQLAlchemy-1.4.40-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1cf03d37819dc17a388d313919daf32058d19ba1e592efdf14ce8cbd997e6023"}, + {file = "SQLAlchemy-1.4.40-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:a62c0ecbb9976550f26f7bf75569f425e661e7249349487f1483115e5fc893a6"}, + {file = "SQLAlchemy-1.4.40-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4ec440990ab00650d0c7ea2c75bc225087afdd7ddcb248e3d934def4dff62762"}, + {file = "SQLAlchemy-1.4.40-cp38-cp38-win32.whl", hash = "sha256:2b64955850a14b9d481c17becf0d3f62fb1bb31ac2c45c2caf5ad06d9e811187"}, + {file = "SQLAlchemy-1.4.40-cp38-cp38-win_amd64.whl", hash = "sha256:959bf4390766a8696aa01285016c766b4eb676f712878aac5fce956dd49695d9"}, + {file = "SQLAlchemy-1.4.40-cp39-cp39-macosx_10_15_x86_64.whl", hash = "sha256:0992f3cc640ec0f88f721e426da884c34ff0a60eb73d3d64172e23dfadfc8a0b"}, + {file = "SQLAlchemy-1.4.40-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fa9e0d7832b7511b3b3fd0e67fac85ff11fd752834c143ca2364c9b778c0485a"}, + {file = "SQLAlchemy-1.4.40-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:c9d0f1a9538cc5e75f2ea0cb6c3d70155a1b7f18092c052e0d84105622a41b63"}, + {file = "SQLAlchemy-1.4.40-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0c956a5d1adb49a35d78ef0fae26717afc48a36262359bb5b0cbd7a3a247c26f"}, + {file = "SQLAlchemy-1.4.40-cp39-cp39-win32.whl", hash = "sha256:6b70d02bbe1adbbf715d2249cacf9ac17c6f8d22dfcb3f1a4fbc5bf64364da8a"}, + {file = "SQLAlchemy-1.4.40-cp39-cp39-win_amd64.whl", hash = "sha256:bf073c619b5a7f7cd731507d0fdc7329bee14b247a63b0419929e4acd24afea8"}, + {file = "SQLAlchemy-1.4.40.tar.gz", hash = "sha256:44a660506080cc975e1dfa5776fe5f6315ddc626a77b50bf0eee18b0389ea265"}, +] +tangled-up-in-unicode = [ + {file = "tangled_up_in_unicode-0.2.0-py3-none-any.whl", hash = "sha256:154be12605b1687a17133aa741ae951cf9ee531c48a0c19f98d83ec5cb3cc7be"}, + {file = "tangled_up_in_unicode-0.2.0.tar.gz", hash = "sha256:de56dbaf32de1b8c65621f97b06b7de21cba18c8e61f3988a426ffe3c40fed36"}, +] +text-unidecode = [ + {file = "text-unidecode-1.3.tar.gz", hash = "sha256:bad6603bb14d279193107714b288be206cac565dfa49aa5b105294dd5c4aab93"}, + {file = "text_unidecode-1.3-py2.py3-none-any.whl", hash = "sha256:1311f10e8b895935241623731c2ba64f4c455287888b18189350b67134a822e8"}, +] +toml = [ + {file = "toml-0.10.2-py2.py3-none-any.whl", hash = "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b"}, + {file = "toml-0.10.2.tar.gz", hash = "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f"}, +] +tomli = [ + {file = "tomli-2.0.1-py3-none-any.whl", hash = "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc"}, + {file = "tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"}, +] +tqdm = [ + {file = "tqdm-4.64.0-py2.py3-none-any.whl", hash = "sha256:74a2cdefe14d11442cedf3ba4e21a3b84ff9a2dbdc6cfae2c34addb2a14a5ea6"}, + {file = "tqdm-4.64.0.tar.gz", hash = "sha256:40be55d30e200777a307a7585aee69e4eabb46b4ec6a4b4a5f2d9f11e7d5408d"}, +] +typing-extensions = [ + {file = "typing_extensions-4.3.0-py3-none-any.whl", hash = "sha256:25642c956049920a5aa49edcdd6ab1e06d7e5d467fc00e0506c44ac86fbfca02"}, + {file = "typing_extensions-4.3.0.tar.gz", hash = "sha256:e6d2677a32f47fc7eb2795db1dd15c1f34eff616bcaf2cfb5e997f854fa1c4a6"}, +] +udata-event-service = [ + {file = "udata_event_service-0.0.8-py2.py3-none-any.whl", hash = "sha256:35ea65522fb3ed98c866f2cbe5d9ac5562949c549f5be8657aaecb50088f40d2"}, + {file = "udata_event_service-0.0.8.tar.gz", hash = "sha256:27e026cd85b432f65d99010435c2eccedc3d37aecad2128d7e9be650c4b25178"}, +] +urllib3 = [ + {file = "urllib3-1.26.12-py2.py3-none-any.whl", hash = "sha256:b930dd878d5a8afb066a637fbb35144fe7901e3b209d1cd4f524bd0e9deee997"}, + {file = "urllib3-1.26.12.tar.gz", hash = "sha256:3fa96cf423e6987997fc326ae8df396db2a8b7c667747d47ddd8ecba91f4a74e"}, +] +validators = [ + {file = "validators-0.18.2-py3-none-any.whl", hash = "sha256:0143dcca8a386498edaf5780cbd5960da1a4c85e0719f3ee5c9b41249c4fefbd"}, + {file = "validators-0.18.2.tar.gz", hash = "sha256:37cd9a9213278538ad09b5b9f9134266e7c226ab1fede1d500e29e0a8fbb9ea6"}, +] +visions = [ + {file = "visions-0.7.4-py3-none-any.whl", hash = "sha256:16f007e70362b788478b95129bb044dc0718b7530205ebddd8a1ff34a281d28e"}, + {file = "visions-0.7.4.tar.gz", hash = "sha256:4f66c1da369cdbced5e1e2d2c43104caa8cfb9c694006cea1592e1673cfc1153"}, +] +werkzeug = [ + {file = "Werkzeug-1.0.1-py2.py3-none-any.whl", hash = "sha256:2de2a5db0baeae7b2d2664949077c2ac63fbd16d98da0ff71837f7d1dea3fd43"}, + {file = "Werkzeug-1.0.1.tar.gz", hash = "sha256:6c80b1e5ad3665290ea39320b91e1be1e0d5f60652b964a3070216de83d2e47c"}, +] +wsproto = [ + {file = "wsproto-1.2.0-py3-none-any.whl", hash = "sha256:b9acddd652b585d75b20477888c56642fdade28bdfd3579aa24a4d2c037dd736"}, + {file = "wsproto-1.2.0.tar.gz", hash = "sha256:ad565f26ecb92588a3e43bc3d96164de84cd9902482b130d0ddbaa9664a85065"}, +] +xlrd = [ + {file = "xlrd-2.0.1-py2.py3-none-any.whl", hash = "sha256:6a33ee89877bd9abc1158129f6e94be74e2679636b8a205b43b85206c3f0bbdd"}, + {file = "xlrd-2.0.1.tar.gz", hash = "sha256:f72f148f54442c6b056bf931dbc34f986fd0c3b0b6b5a58d013c9aef274d0c88"}, +] +yarl = [ + {file = "yarl-1.8.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:abc06b97407868ef38f3d172762f4069323de52f2b70d133d096a48d72215d28"}, + {file = "yarl-1.8.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:07b21e274de4c637f3e3b7104694e53260b5fc10d51fb3ec5fed1da8e0f754e3"}, + {file = "yarl-1.8.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9de955d98e02fab288c7718662afb33aab64212ecb368c5dc866d9a57bf48880"}, + {file = "yarl-1.8.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7ec362167e2c9fd178f82f252b6d97669d7245695dc057ee182118042026da40"}, + {file = "yarl-1.8.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:20df6ff4089bc86e4a66e3b1380460f864df3dd9dccaf88d6b3385d24405893b"}, + {file = "yarl-1.8.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5999c4662631cb798496535afbd837a102859568adc67d75d2045e31ec3ac497"}, + {file = "yarl-1.8.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ed19b74e81b10b592084a5ad1e70f845f0aacb57577018d31de064e71ffa267a"}, + {file = "yarl-1.8.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1e4808f996ca39a6463f45182e2af2fae55e2560be586d447ce8016f389f626f"}, + {file = "yarl-1.8.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:2d800b9c2eaf0684c08be5f50e52bfa2aa920e7163c2ea43f4f431e829b4f0fd"}, + {file = "yarl-1.8.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:6628d750041550c5d9da50bb40b5cf28a2e63b9388bac10fedd4f19236ef4957"}, + {file = "yarl-1.8.1-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:f5af52738e225fcc526ae64071b7e5342abe03f42e0e8918227b38c9aa711e28"}, + {file = "yarl-1.8.1-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:76577f13333b4fe345c3704811ac7509b31499132ff0181f25ee26619de2c843"}, + {file = "yarl-1.8.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:0c03f456522d1ec815893d85fccb5def01ffaa74c1b16ff30f8aaa03eb21e453"}, + {file = "yarl-1.8.1-cp310-cp310-win32.whl", hash = "sha256:ea30a42dc94d42f2ba4d0f7c0ffb4f4f9baa1b23045910c0c32df9c9902cb272"}, + {file = "yarl-1.8.1-cp310-cp310-win_amd64.whl", hash = "sha256:9130ddf1ae9978abe63808b6b60a897e41fccb834408cde79522feb37fb72fb0"}, + {file = "yarl-1.8.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:0ab5a138211c1c366404d912824bdcf5545ccba5b3ff52c42c4af4cbdc2c5035"}, + {file = "yarl-1.8.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a0fb2cb4204ddb456a8e32381f9a90000429489a25f64e817e6ff94879d432fc"}, + {file = "yarl-1.8.1-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:85cba594433915d5c9a0d14b24cfba0339f57a2fff203a5d4fd070e593307d0b"}, + {file = "yarl-1.8.1-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1ca7e596c55bd675432b11320b4eacc62310c2145d6801a1f8e9ad160685a231"}, + {file = "yarl-1.8.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d0f77539733e0ec2475ddcd4e26777d08996f8cd55d2aef82ec4d3896687abda"}, + {file = "yarl-1.8.1-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:29e256649f42771829974e742061c3501cc50cf16e63f91ed8d1bf98242e5507"}, + {file = "yarl-1.8.1-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:7fce6cbc6c170ede0221cc8c91b285f7f3c8b9fe28283b51885ff621bbe0f8ee"}, + {file = "yarl-1.8.1-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:59ddd85a1214862ce7c7c66457f05543b6a275b70a65de366030d56159a979f0"}, + {file = "yarl-1.8.1-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:12768232751689c1a89b0376a96a32bc7633c08da45ad985d0c49ede691f5c0d"}, + {file = "yarl-1.8.1-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:b19255dde4b4f4c32e012038f2c169bb72e7f081552bea4641cab4d88bc409dd"}, + {file = "yarl-1.8.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:6c8148e0b52bf9535c40c48faebb00cb294ee577ca069d21bd5c48d302a83780"}, + {file = "yarl-1.8.1-cp37-cp37m-win32.whl", hash = "sha256:de839c3a1826a909fdbfe05f6fe2167c4ab033f1133757b5936efe2f84904c07"}, + {file = "yarl-1.8.1-cp37-cp37m-win_amd64.whl", hash = "sha256:dd032e8422a52e5a4860e062eb84ac94ea08861d334a4bcaf142a63ce8ad4802"}, + {file = "yarl-1.8.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:19cd801d6f983918a3f3a39f3a45b553c015c5aac92ccd1fac619bd74beece4a"}, + {file = "yarl-1.8.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:6347f1a58e658b97b0a0d1ff7658a03cb79bdbda0331603bed24dd7054a6dea1"}, + {file = "yarl-1.8.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:7c0da7e44d0c9108d8b98469338705e07f4bb7dab96dbd8fa4e91b337db42548"}, + {file = "yarl-1.8.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5587bba41399854703212b87071c6d8638fa6e61656385875f8c6dff92b2e461"}, + {file = "yarl-1.8.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:31a9a04ecccd6b03e2b0e12e82131f1488dea5555a13a4d32f064e22a6003cfe"}, + {file = "yarl-1.8.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:205904cffd69ae972a1707a1bd3ea7cded594b1d773a0ce66714edf17833cdae"}, + {file = "yarl-1.8.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ea513a25976d21733bff523e0ca836ef1679630ef4ad22d46987d04b372d57fc"}, + {file = "yarl-1.8.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d0b51530877d3ad7a8d47b2fff0c8df3b8f3b8deddf057379ba50b13df2a5eae"}, + {file = "yarl-1.8.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:d2b8f245dad9e331540c350285910b20dd913dc86d4ee410c11d48523c4fd546"}, + {file = "yarl-1.8.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:ab2a60d57ca88e1d4ca34a10e9fb4ab2ac5ad315543351de3a612bbb0560bead"}, + {file = "yarl-1.8.1-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:449c957ffc6bc2309e1fbe67ab7d2c1efca89d3f4912baeb8ead207bb3cc1cd4"}, + {file = "yarl-1.8.1-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:a165442348c211b5dea67c0206fc61366212d7082ba8118c8c5c1c853ea4d82e"}, + {file = "yarl-1.8.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:b3ded839a5c5608eec8b6f9ae9a62cb22cd037ea97c627f38ae0841a48f09eae"}, + {file = "yarl-1.8.1-cp38-cp38-win32.whl", hash = "sha256:c1445a0c562ed561d06d8cbc5c8916c6008a31c60bc3655cdd2de1d3bf5174a0"}, + {file = "yarl-1.8.1-cp38-cp38-win_amd64.whl", hash = "sha256:56c11efb0a89700987d05597b08a1efcd78d74c52febe530126785e1b1a285f4"}, + {file = "yarl-1.8.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:e80ed5a9939ceb6fda42811542f31c8602be336b1fb977bccb012e83da7e4936"}, + {file = "yarl-1.8.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:6afb336e23a793cd3b6476c30f030a0d4c7539cd81649683b5e0c1b0ab0bf350"}, + {file = "yarl-1.8.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:4c322cbaa4ed78a8aac89b2174a6df398faf50e5fc12c4c191c40c59d5e28357"}, + {file = "yarl-1.8.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fae37373155f5ef9b403ab48af5136ae9851151f7aacd9926251ab26b953118b"}, + {file = "yarl-1.8.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5395da939ffa959974577eff2cbfc24b004a2fb6c346918f39966a5786874e54"}, + {file = "yarl-1.8.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:076eede537ab978b605f41db79a56cad2e7efeea2aa6e0fa8f05a26c24a034fb"}, + {file = "yarl-1.8.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3d1a50e461615747dd93c099f297c1994d472b0f4d2db8a64e55b1edf704ec1c"}, + {file = "yarl-1.8.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7de89c8456525650ffa2bb56a3eee6af891e98f498babd43ae307bd42dca98f6"}, + {file = "yarl-1.8.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:4a88510731cd8d4befaba5fbd734a7dd914de5ab8132a5b3dde0bbd6c9476c64"}, + {file = "yarl-1.8.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:2d93a049d29df172f48bcb09acf9226318e712ce67374f893b460b42cc1380ae"}, + {file = "yarl-1.8.1-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:21ac44b763e0eec15746a3d440f5e09ad2ecc8b5f6dcd3ea8cb4773d6d4703e3"}, + {file = "yarl-1.8.1-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:d0272228fabe78ce00a3365ffffd6f643f57a91043e119c289aaba202f4095b0"}, + {file = "yarl-1.8.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:99449cd5366fe4608e7226c6cae80873296dfa0cde45d9b498fefa1de315a09e"}, + {file = "yarl-1.8.1-cp39-cp39-win32.whl", hash = "sha256:8b0af1cf36b93cee99a31a545fe91d08223e64390c5ecc5e94c39511832a4bb6"}, + {file = "yarl-1.8.1-cp39-cp39-win_amd64.whl", hash = "sha256:de49d77e968de6626ba7ef4472323f9d2e5a56c1d85b7c0e2a190b2173d3b9be"}, + {file = "yarl-1.8.1.tar.gz", hash = "sha256:af887845b8c2e060eb5605ff72b6f2dd2aab7a761379373fd89d314f4752abbf"}, +] diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..29ee42a --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,43 @@ +[tool.poetry] +name = "csvapi" +version = "1.3.0.dev" +description = "An instant JSON API for your CSV" +authors = ["Opendatateam "] +license = "MIT" + +[tool.poetry.dependencies] +python = ">=3.9,<4" +click_default_group = "~1.2.2" +click = "~8.1.3" +agate = "~1.6.1" +agate-sql = "~0.5.5" +aiohttp = "~3.7.3" +validators = "~0.18.2" +agate-excel = "~0.2.3" +Quart = "~0.14.1" +quart-cors = "~0.3.0" +sentry-sdk = "~1.0.0" +cchardet = "~2.1.7" +python-stdnum = "~1.15" +aiosqlite = "~0.16.1" +pandas = "~1.4.3" +pandas-profiling = "~3.2.0" +requests = "~2.27.1" +udata-event-service = "~0.0.8" +boto3 = "~1.21.21" +Jinja2 = "~3.0.3" +Werkzeug = "~1.0.0" + +[tool.poetry.dev-dependencies] +aioresponses = "~0.7.1" +pytest = "~7.1.2" +pytest-asyncio = "~0.19.0" +flake8 = "~5.0.4" +pytest-cov = "~2.5.1" + +[tool.poetry.scripts] +csvapi = "csvapi.cli:cli" + +[build-system] +requires = ["poetry-core>=1.0.0"] +build-backend = "poetry.core.masonry.api" diff --git a/requirements/develop.pip b/requirements/develop.pip deleted file mode 100644 index 4a0b60a..0000000 --- a/requirements/develop.pip +++ /dev/null @@ -1,5 +0,0 @@ --e .[test] -flake8==3.5.0 -invoke==1.1.1 -pytest-cov==2.5.1 -wheel \ No newline at end of file diff --git a/requirements/install.pip b/requirements/install.pip deleted file mode 100644 index 97c394b..0000000 --- a/requirements/install.pip +++ /dev/null @@ -1,18 +0,0 @@ -click_default_group==1.2.2 -click==8.1.3 -agate==1.6.1 -agate-sql==0.5.5 -aiohttp==3.7.3 -validators==0.18.2 -agate-excel==0.2.3 -Quart==0.14.1 -quart-cors==0.3.0 -sentry-sdk==1.0.0 -cchardet==2.1.7 -python-stdnum==1.15 -aiosqlite==0.16.1 -pandas==1.4.2 -pandas-profiling==2.11.0 -requests==2.27.1 -udata-event-service==0.0.8 -boto3==1.21.21 diff --git a/requirements/test.pip b/requirements/test.pip deleted file mode 100644 index 8da3214..0000000 --- a/requirements/test.pip +++ /dev/null @@ -1,3 +0,0 @@ -aioresponses==0.7.1 -pytest==6.2.2 -pytest-asyncio==0.14.0 diff --git a/setup.py b/setup.py deleted file mode 100644 index 4278ff3..0000000 --- a/setup.py +++ /dev/null @@ -1,85 +0,0 @@ -#!/usr/bin/env python -import io -import os -import re - -from setuptools import setup, find_packages - -RE_REQUIREMENT = re.compile(r'^\s*-r\s*(?P.*)$') -RE_BADGE = re.compile(r'^\[\!\[(?P[^\]]+)\]\[(?P[^\]]+)\]\]\[(?P[^\]]+)\]$', re.M) - -BADGES_TO_KEEP = ['gitter-badge', 'readthedocs-badge'] - - -def md(filename): - ''' - Load .md (markdown) file and sanitize it for PyPI. - Remove unsupported github tags: - - code-block directive - - travis ci build badges - ''' - content = io.open(filename).read() - - for match in RE_BADGE.finditer(content): - if match.group('badge') not in BADGES_TO_KEEP: - content = content.replace(match.group(0), '') - return content - - -long_description = '\n'.join(( - md('README.md'), - md('CHANGELOG.md'), - '' -)) - - -def pip(filename): - """Parse pip reqs file and transform it to setuptools requirements.""" - requirements = [] - for line in open(os.path.join('requirements', filename)): - line = line.strip() - if not line or '://' in line or line.startswith('#'): - continue - match = RE_REQUIREMENT.match(line) - if match: - requirements.extend(pip(match.group('filename'))) - else: - requirements.append(line) - return requirements - - -install_requires = pip('install.pip') -tests_require = pip('test.pip') - -setup( - name='csvapi', - python_requires='>=3.6.1', - description='An instant JSON API for your CSV', - long_description=long_description, - long_description_content_type='text/markdown', - author='Opendata Team', - author_email='opendatateam@data.gouv.fr', - version='1.2.2.dev', - license='MIT', - url='https://github.com/opendatateam/csvapi', - packages=find_packages(), - package_data={'csvapi': []}, - include_package_data=True, - install_requires=install_requires, - tests_require=tests_require, - extras_require={ - 'test': tests_require, - }, - entry_points=''' - [console_scripts] - csvapi=csvapi.cli:cli - ''', - classifiers=[ - 'Development Status :: 3 - Alpha', - 'License :: OSI Approved :: MIT License', - 'Programming Language :: Python', - 'Programming Language :: Python :: 3', - 'Programming Language :: Python :: 3.6', - 'Programming Language :: Python :: Implementation :: CPython', - ], -) diff --git a/tasks.py b/tasks.py deleted file mode 100644 index 2c96029..0000000 --- a/tasks.py +++ /dev/null @@ -1,101 +0,0 @@ -import os - -from invoke import task, call - -ROOT = os.path.abspath(os.path.join(os.path.dirname(__file__))) - -TO_CLEAN = ['build', 'dist', '**/*.pyc', 'reports', '.cache'] - - -def color(code): - '''A simple ANSI color wrapper factory''' - return lambda t: '\033[{0}{1}\033[0;m'.format(code, t) - - -green = color('1;32m') -red = color('1;31m') -blue = color('1;30m') -cyan = color('1;36m') -purple = color('1;35m') -white = color('1;39m') - - -def header(text): - '''Display an header''' - print(' '.join((blue('>>'), cyan(text)))) - - -def info(text, *args, **kwargs): - '''Display informations''' - text = text.format(*args, **kwargs) - print(' '.join((purple('>>>'), text))) - - -def success(text): - '''Display a success message''' - print(' '.join((green('>>'), white(text)))) - - -def error(text): - '''Display an error message''' - print(red('✘ {0}'.format(text))) - - -@task -def clean(ctx): - '''Cleanup all build artifacts''' - header(clean.__doc__) - with ctx.cd(ROOT): - for pattern in TO_CLEAN: - info('Removing {0}', pattern) - ctx.run('rm -rf {0}'.format(pattern)) - - -@task -def test(ctx, report=False): - '''Run tests suite''' - cmd = 'pytest tests/ -v' - if report: - cmd = ' '.join((cmd, '--junitxml=reports/tests.xml')) - with ctx.cd(ROOT): - ctx.run(cmd, pty=True) - - -@task -def cover(ctx, html=False): - '''Run tests suite with coverage''' - cmd = 'pytest --cov csvapi --cov-report term' - if html: - cmd = ' '.join((cmd, '--cov-report html:reports/cover')) - with ctx.cd(ROOT): - ctx.run(cmd, pty=True) - - -@task -def qa(ctx): - '''Run a quality report''' - header('Performing static analysis') - flake8_results = ctx.run('flake8 csvapi', pty=True, warn=True) - if flake8_results.failed: - error('There are some lints to fix') - exit(flake8_results.return_code) - print(green('OK')) - - -@task -def dist(ctx, buildno=None): - '''Package for distribution''' - header('Building a distribuable package') - cmd = ['python setup.py'] - if buildno: - cmd.append('egg_info -b {0}'.format(buildno)) - cmd.append('bdist_wheel') - with ctx.cd(ROOT): - ctx.run(' '.join(cmd), pty=True) - success('Distribution is available in dist directory') - - -@task(clean, qa, call(test, report=True), dist, default=True) -def default(ctx): - '''Perform quality report, tests and packaging''' - pass diff --git a/tests/test_api.py b/tests/test_api.py index 39bf477..ad52cd0 100644 --- a/tests/test_api.py +++ b/tests/test_api.py @@ -3,6 +3,7 @@ from pathlib import Path import pytest +import pytest_asyncio from aioresponses import aioresponses from csvapi.utils import get_hash @@ -105,7 +106,7 @@ def random_url(): return f"https://example.com/{uuid.uuid4()}.csv" -@pytest.fixture +@pytest_asyncio.fixture async def uploaded_csv(rmock, csv, client): content = csv.replace('', ';').encode('utf-8') rmock.get(MOCK_CSV_URL, body=content) @@ -396,7 +397,7 @@ async def test_real_xls_files(client, rmock, xls_path): assert len(jsonres['rows']) > 0 -@pytest.fixture +@pytest_asyncio.fixture async def uploaded_csv_filters(rmock, csv_filters, client): content = csv_filters.encode('utf-8') rmock.get(MOCK_CSV_URL_FILTERS, body=content) From a83e64550ca2f7558ca1c02167f737adbc97bf55 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alexandre=20Bult=C3=A9?= Date: Fri, 26 Aug 2022 11:11:59 +0200 Subject: [PATCH 13/14] trigger build From 41678a10a163226ca1873c42d2cc375cae885113 Mon Sep 17 00:00:00 2001 From: maudetes Date: Tue, 6 Sep 2022 14:14:49 +0200 Subject: [PATCH 14/14] Perf modifications (#98) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Check message structure to prevent errors * Add pandas profiling analysis (optional) in api * Update requirements (csv-detective) * Update message structure format * Add requirements * Remove requirements, switch to poetry * Add poetry lock file * Lint code * setuptools * upgrade and clean deps * lint test Co-authored-by: Geoffrey Aldebert Co-authored-by: Alexandre Bulté --- .vscode/settings.json | 5 + config.py | 2 +- csvapi/consumer.py | 259 ++++++----------- csvapi/parser.py | 1 + csvapi/parseview.py | 63 ++++- csvapi/utils.py | 173 +++++++++++- csvapi/webservice.py | 2 +- poetry.lock | 626 +++++++++++++++++++++++++++--------------- pyproject.toml | 35 ++- tests/test_api.py | 12 +- 10 files changed, 762 insertions(+), 416 deletions(-) create mode 100644 .vscode/settings.json diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..9e2481a --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,5 @@ +{ + "python.pythonPath": "/Users/geoffrey/.pyenv/versions/3.9.7/envs/csvapi/bin/python", + "python.linting.enabled": true, + "python.linting.flake8Args": ["--ignore=E24,W504", "--verbose"] +} \ No newline at end of file diff --git a/config.py b/config.py index 9e5604a..990fb26 100644 --- a/config.py +++ b/config.py @@ -14,4 +14,4 @@ # It will also match subdomains # e.g. REFERRERS_FILTER = ['data.gouv.fr'] will match 'demo.data.gouv.fr' REFERRERS_FILTER = None -PANDAS_PROFILING_CONFIG_MIN = False \ No newline at end of file +PANDAS_PROFILING_CONFIG_MIN = False diff --git a/csvapi/consumer.py b/csvapi/consumer.py index 803f1e3..b3fa439 100644 --- a/csvapi/consumer.py +++ b/csvapi/consumer.py @@ -1,20 +1,19 @@ import asyncio import json import os - -from datetime import datetime - import boto3 -import pandas as pd - from botocore.client import Config, ClientError from csvapi.parseview import ParseView from csvapi.profileview import ProfileView +from csvapi.utils import enrich_db_with_metadata from csvapi.setup_logger import logger -from csvapi.utils import get_hash, create_connection - -from config import DB_ROOT_DIR +from csvapi.utils import ( + get_hash, + check_message_structure, + check_csv_detective_report_structure, + check_profile_report_structure +) MINIO_URL = os.environ.get("MINIO_URL", "http://localhost:9000") MINIO_USER = os.environ.get("MINIO_USER", "minio") @@ -26,167 +25,87 @@ def run_process_message(key: str, data: dict, topic: str) -> None: async def process_message(key: str, message: dict, topic: str) -> None: - # Get url - # Should think if we keep that - # r = requests.get('https://www.data.gouv.fr/api/1/datasets/{}/resources/{}'.format(message['meta']['dataset_id'], key)) # noqa - # url = r.json()['url'] - if message is not None and message['service'] == 'csvdetective': - url = 'https://www.data.gouv.fr/fr/datasets/r/{}'.format(key) - urlhash = get_hash(url) - logger.info(urlhash) - - # Connect to minio - s3_client = boto3.client( - "s3", - endpoint_url=MINIO_URL, - aws_access_key_id=MINIO_USER, - aws_secret_access_key=MINIO_PASSWORD, - config=Config(signature_version="s3v4"), - ) - - try: - s3_client.head_bucket(Bucket=message['value']['data_location']['bucket']) - except ClientError as e: - logger.error(e) - logger.error( - "Bucket {} does not exist or credentials are invalid".format( - message['value']['location']['bucket'] + if message['service'] == "csvdetective": + if check_message_structure(message): + url = 'https://www.data.gouv.fr/fr/datasets/r/{}'.format(key) + urlhash = get_hash(url) + logger.info(urlhash) + + # Connect to minio + s3_client = boto3.client( + "s3", + endpoint_url=MINIO_URL, + aws_access_key_id=MINIO_USER, + aws_secret_access_key=MINIO_PASSWORD, + config=Config(signature_version="s3v4"), + ) + + try: + s3_client.head_bucket(Bucket=message['value']['report_location']['bucket']) + except ClientError as e: + logger.error(e) + logger.error( + "Bucket {} does not exist or credentials are invalid".format( + message['value']['report_location']['bucket'] + ) + ) + return + + # Load csv-detective report + try: + response = s3_client.get_object( + Bucket=message['value']['report_location']['bucket'], + Key=message['value']['report_location']['key'] + ) + content = response['Body'] + csv_detective_report = json.loads(content.read()) + except ClientError as e: + logger.error(e) + logger.error( + "Report does not exist in bucket or content is not valid json" ) + return + + if not check_csv_detective_report_structure(csv_detective_report): + logger.error( + "csvdetective report malformed" + ) + return + + # Parse file and store it to sqlite + parseViewInstance = ParseView() + await parseViewInstance.parse_from_consumer( + parseViewInstance, + url=url, + urlhash=urlhash, + csv_detective_report=csv_detective_report ) - return - - # Load csv-detective report - response = s3_client.get_object( - Bucket=message['value']['report_location']['bucket'], - Key=message['value']['report_location']['key'] - ) - content = response['Body'] - csv_detective_report = json.loads(content.read()) - - # Parse file and store it to sqlite - parseViewInstance = ParseView() - await parseViewInstance.parse_from_consumer( - parseViewInstance, - url=url, - urlhash=urlhash, - csv_detective_report=csv_detective_report, - ) - - # Profile file - profileViewInstance = ProfileView() - profile_report = await profileViewInstance.get_minimal_profile( - profileViewInstance, - urlhash=urlhash, - csv_detective_report=csv_detective_report, - ) - - # Save to sql - conn = create_connection(DB_ROOT_DIR+'/'+urlhash+'.db') - # c = conn.cursor() - - general_infos = [ - { - 'encoding': csv_detective_report['encoding'], - 'separator': csv_detective_report['separator'], - 'header_row_idx': csv_detective_report['header_row_idx'], - 'total_lines': profile_report['table']['n'], - 'nb_columns': profile_report['table']['n_var'], - 'nb_cells_missing': profile_report['table']['n_cells_missing'], - 'nb_vars_with_missing': profile_report['table']['n_vars_with_missing'], - 'nb_vars_all_missing': profile_report['table']['n_vars_all_missing'], - 'date_last_check': datetime.today().strftime('%Y-%m-%d'), - 'dataset_id': message['meta']['dataset_id'], - 'resource_id': key - } - ] - df = pd.DataFrame(general_infos) - df.to_sql('general_infos', con=conn, if_exists='replace', index=False) - - columns_infos = [] - categorical_infos = [] - top_infos = [] - numeric_infos = [] - numeric_plot_infos = [] - for col in profile_report['variables']: - column_info = {} - column_info['name'] = col - column_info['format'] = csv_detective_report['columns'][col]['format'] - column_info['nb_distinct'] = profile_report['variables'][col]['n_distinct'] - column_info['is_unique'] = profile_report['variables'][col]['is_unique'] - column_info['nb_unique'] = profile_report['variables'][col]['n_unique'] - column_info['type'] = profile_report['variables'][col]['type'] - column_info['nb_missing'] = profile_report['variables'][col]['n_missing'] - column_info['count'] = profile_report['variables'][col]['count'] - columns_infos.append(column_info) - - cat_cols = [ - 'siren', 'siret', 'code_postal', 'code_commune_insee', - 'code_departement', 'code_region', 'tel_fr', - ] - if csv_detective_report['columns'][col]['format'] in cat_cols: - column_info['type'] = 'Categorical' - - if column_info['type'] == 'Categorical' and \ - len(profile_report['variables'][col]['value_counts_without_nan']) < 10: - for cat in profile_report['variables'][col]['value_counts_without_nan']: - categorical_info = {} - categorical_info['column'] = col - categorical_info['value'] = cat - categorical_info['nb'] = profile_report['variables'][col]['value_counts_without_nan'][cat] - categorical_infos.append(categorical_info) - - if column_info['type'] == 'Numeric': - numeric_info = {} - numeric_info['column'] = col - numeric_info['mean'] = profile_report['variables'][col]['mean'] - numeric_info['std'] = profile_report['variables'][col]['std'] - numeric_info['min'] = profile_report['variables'][col]['min'] - numeric_info['max'] = profile_report['variables'][col]['max'] - numeric_infos.append(numeric_info) - for i in range(len(profile_report['variables'][col]['histogram']['bin_edges'])): - numeric_plot_info = {} - numeric_plot_info['column'] = col - numeric_plot_info['value'] = profile_report['variables'][col]['histogram']['bin_edges'][i] - numeric_plot_info['type'] = 'bin_edges' - numeric_plot_infos.append(numeric_plot_info) - - for i in range(len(profile_report['variables'][col]['histogram']['counts'])): - numeric_plot_info = {} - numeric_plot_info['column'] = col - numeric_plot_info['value'] = profile_report['variables'][col]['histogram']['counts'][i] - numeric_plot_info['type'] = 'counts' - numeric_plot_infos.append(numeric_plot_info) - - cpt = 0 - for top in profile_report['variables'][col]['value_counts_without_nan']: - if (cpt < 10): - top_info = {} - top_info['column'] = col - top_info['value'] = top - top_info['nb'] = profile_report['variables'][col]['value_counts_without_nan'][top] - top_infos.append(top_info) - cpt = cpt + 1 - - df = pd.DataFrame(columns_infos) - if df.shape[0] > 0: - df.to_sql('columns_infos', con=conn, if_exists='replace', index=False) - - df = pd.DataFrame(categorical_infos) - if df.shape[0] > 0: - df.to_sql('categorical_infos', con=conn, if_exists='replace', index=False) - - df = pd.DataFrame(top_infos) - if df.shape[0] > 0: - df.to_sql('top_infos', con=conn, if_exists='replace', index=False) - - df = pd.DataFrame(numeric_infos) - if df.shape[0] > 0: - df.to_sql('numeric_infos', con=conn, if_exists='replace', index=False) - - df = pd.DataFrame(numeric_plot_infos) - if df.shape[0] > 0: - df.to_sql('numeric_plot_infos', con=conn, if_exists='replace', index=False) - - conn.commit() - - print('ok') + + # Profile file + profileViewInstance = ProfileView() + profile_report = await profileViewInstance.get_minimal_profile( + profileViewInstance, + urlhash=urlhash, + csv_detective_report=csv_detective_report + ) + + if not check_profile_report_structure(profile_report): + logger.error( + "pandas profiling report malformed" + ) + return + + enrich_db_with_metadata( + urlhash, + csv_detective_report, + profile_report, + message['meta']['dataset_id'], + key + ) + + logger.info('Enrichment done!') + + else: + logger.error('Problem with structure message') + else: + logger.info('Message received from {} service - do not process'.format(message['service'])) diff --git a/csvapi/parser.py b/csvapi/parser.py index e6d0312..21051f0 100644 --- a/csvapi/parser.py +++ b/csvapi/parser.py @@ -55,6 +55,7 @@ def from_csv(filepath, encoding='utf-8', sniff_limit=SNIFF_LIMIT, agate_types=No 'encoding': encoding, 'column_types': agate_tester() } + logger.warning('Types from csv-detective provoke errors, use of agate type tester instead.') return agate.Table.from_csv(filepath, **kwargs) except Exception as e: logger.error('error casting %s', e) diff --git a/csvapi/parseview.py b/csvapi/parseview.py index 784addc..3b2e15b 100644 --- a/csvapi/parseview.py +++ b/csvapi/parseview.py @@ -7,14 +7,24 @@ from quart import request, jsonify, current_app as app from quart.views import MethodView +from csvapi.profileview import ProfileView + from csvapi.errors import APIError from csvapi.parser import parse -from csvapi.utils import already_exists, get_hash +from csvapi.utils import ( + already_exists, + get_hash, + check_csv_detective_report_structure, + check_profile_report_structure, + enrich_db_with_metadata +) from csvapi.setup_logger import logger from csvapi.type_tester import convert_types from config import DB_ROOT_DIR, CSV_SNIFF_LIMIT, MAX_FILE_SIZE +from csv_detective.explore_csv import routine + class ParseView(MethodView): @@ -38,7 +48,17 @@ async def parse_from_consumer(self, url: str, urlhash: str, csv_detective_report ) @staticmethod - async def do_parse(url, urlhash, encoding, storage, logger, sniff_limit, max_file_size, agate_types=None): + async def do_parse( + url, + urlhash, + encoding, + storage, + logger, + sniff_limit, + max_file_size, + agate_types=None, + analysis=None + ): logger.debug('* do_parse %s (%s)', urlhash, url) tmp = tempfile.NamedTemporaryFile(delete=False) chunk_count = 0 @@ -56,9 +76,42 @@ async def do_parse(url, urlhash, encoding, storage, logger, sniff_limit, max_fil tmp.write(chunk) chunk_count += 1 tmp.close() + logger.debug('* Downloaded %s', urlhash) logger.debug('* Parsing %s...', urlhash) parse(tmp.name, urlhash, storage, encoding=encoding, sniff_limit=sniff_limit, agate_types=agate_types) + + if analysis and analysis == 'yes': + csv_detective_report = routine(tmp.name) + logger.info(csv_detective_report) + + if not check_csv_detective_report_structure(csv_detective_report): + logger.error( + "csvdetective report malformed" + ) + return + + profileViewInstance = ProfileView() + profile_report = await profileViewInstance.get_minimal_profile( + profileViewInstance, + urlhash=urlhash, + csv_detective_report=csv_detective_report + ) + + if not check_profile_report_structure(profile_report): + logger.error( + "pandas profiling report malformed" + ) + return + + enrich_db_with_metadata( + urlhash, + csv_detective_report, + profile_report, + None, + None + ) + logger.debug('* Parsed %s', urlhash) finally: logger.debug('Removing tmp file: %s', tmp.name) @@ -73,7 +126,7 @@ async def get(self): if not validators.url(url): raise APIError('Malformed url parameter.', status=400) urlhash = get_hash(url) - + analysis = request.args.get('analysis') if not already_exists(urlhash): try: storage = app.config['DB_ROOT_DIR'] @@ -83,8 +136,8 @@ async def get(self): storage=storage, logger=app.logger, sniff_limit=app.config.get('CSV_SNIFF_LIMIT'), - max_file_size=app.config.get('MAX_FILE_SIZE') - ) + max_file_size=app.config.get('MAX_FILE_SIZE'), + analysis=analysis) except Exception as e: raise APIError('Error parsing CSV: %s' % e) else: diff --git a/csvapi/utils.py b/csvapi/utils.py index 6ba06e5..0d374a4 100644 --- a/csvapi/utils.py +++ b/csvapi/utils.py @@ -7,6 +7,8 @@ from config import PROFILES_ROOT_DIR, DB_ROOT_DIR import sqlite3 +from datetime import datetime +import pandas as pd executor = None @@ -47,4 +49,173 @@ def already_exists(urlhash): def create_connection(db_file): - return sqlite3.connect(db_file, uri=True) + conn = None + conn = sqlite3.connect(db_file) + return conn + + +def keys_exists(element, *keys): + ''' + Check if *keys (nested) exists in `element` (dict). + ''' + if not isinstance(element, dict): + raise AttributeError('keys_exists() expects dict as first argument.') + if len(keys) == 0: + raise AttributeError('keys_exists() expects at least two arguments, one given.') + _element = element + for key in keys: + try: + _element = _element[key] + except KeyError: + return False + return True + + +def check_message_structure(message): + if (message is not None) & \ + (keys_exists(message, "service")) & \ + (keys_exists(message, "value", "report_location", "bucket")) & \ + (keys_exists(message, "value", "report_location", "key")) & \ + (keys_exists(message, "meta", "dataset_id")): + return True + else: + return False + + +def check_csv_detective_report_structure(report): + if (report is not None) and \ + (keys_exists(report, "columns")) and \ + (keys_exists(report, "encoding")) and \ + (keys_exists(report, "separator")) and \ + (keys_exists(report, "header_row_idx")): + + for item in report['columns']: + if (not keys_exists(report, "columns", item, "python_type")) | \ + (not keys_exists(report, "columns", item, "format")): + return False + return True + else: + return False + + +def check_profile_report_structure(report): + if (report is not None) and \ + (keys_exists(report, "table", "n")) and \ + (keys_exists(report, "table", "n_var")) and \ + (keys_exists(report, "table", "n_cells_missing")) and \ + (keys_exists(report, "table", "n_vars_with_missing")) and \ + (keys_exists(report, "table", "n_vars_all_missing")) and \ + (keys_exists(report, "table", "n_cells_missing")) and \ + (keys_exists(report, "variables")): + + for item in report['variables']: + if (not keys_exists(report, "variables", item, "n_distinct")) | \ + (not keys_exists(report, "variables", item, "is_unique")) | \ + (not keys_exists(report, "variables", item, "n_unique")) | \ + (not keys_exists(report, "variables", item, "type")) | \ + (not keys_exists(report, "variables", item, "n_missing")) | \ + (not keys_exists(report, "variables", item, "count")): + return False + return True + else: + return False + + +def df_to_sql(obj, conn, name): + df = pd.DataFrame(obj) + if df.shape[0] > 0: + df.to_sql(name, con=conn, if_exists='replace', index=False) + + +def enrich_db_with_metadata(urlhash, csv_detective_report, profile_report, dataset_id, key): + # Save to sql + conn = create_connection(DB_ROOT_DIR + '/' + urlhash + '.db') + # c = conn.cursor() + + general_infos = [ + { + 'encoding': csv_detective_report['encoding'], + 'separator': csv_detective_report['separator'], + 'header_row_idx': csv_detective_report['header_row_idx'], + 'total_lines': profile_report['table']['n'], + 'nb_columns': profile_report['table']['n_var'], + 'nb_cells_missing': profile_report['table']['n_cells_missing'], + 'nb_vars_with_missing': profile_report['table']['n_vars_with_missing'], + 'nb_vars_all_missing': profile_report['table']['n_vars_all_missing'], + 'date_last_check': datetime.today().strftime('%Y-%m-%d'), + 'dataset_id': dataset_id, + 'resource_id': key + } + ] + df = pd.DataFrame(general_infos) + df.to_sql('general_infos', con=conn, if_exists='replace', index=False) + + columns_infos = [] + categorical_infos = [] + top_infos = [] + numeric_infos = [] + numeric_plot_infos = [] + for col in profile_report['variables']: + column_info = {} + column_info['name'] = col + column_info['format'] = csv_detective_report['columns'][col]['format'] + column_info['nb_distinct'] = profile_report['variables'][col]['n_distinct'] + column_info['is_unique'] = profile_report['variables'][col]['is_unique'] + column_info['nb_unique'] = profile_report['variables'][col]['n_unique'] + column_info['type'] = profile_report['variables'][col]['type'] + column_info['nb_missing'] = profile_report['variables'][col]['n_missing'] + column_info['count'] = profile_report['variables'][col]['count'] + columns_infos.append(column_info) + + if csv_detective_report['columns'][col]['format'] in \ + ['siren', 'siret', 'code_postal', 'code_commune_insee', 'code_departement', 'code_region', 'tel_fr']: + column_info['type'] = 'Categorical' + + if (column_info['type'] == 'Categorical') & \ + (len(profile_report['variables'][col]['value_counts_without_nan']) < 10): + for cat in profile_report['variables'][col]['value_counts_without_nan']: + categorical_info = {} + categorical_info['column'] = col + categorical_info['value'] = cat + categorical_info['nb'] = profile_report['variables'][col]['value_counts_without_nan'][cat] + categorical_infos.append(categorical_info) + + if column_info['type'] == 'Numeric': + numeric_info = {} + numeric_info['column'] = col + numeric_info['mean'] = profile_report['variables'][col]['mean'] + numeric_info['std'] = profile_report['variables'][col]['std'] + numeric_info['min'] = profile_report['variables'][col]['min'] + numeric_info['max'] = profile_report['variables'][col]['max'] + numeric_infos.append(numeric_info) + for i in range(len(profile_report['variables'][col]['histogram']['bin_edges'])): + numeric_plot_info = {} + numeric_plot_info['column'] = col + numeric_plot_info['value'] = profile_report['variables'][col]['histogram']['bin_edges'][i] + numeric_plot_info['type'] = 'bin_edges' + numeric_plot_infos.append(numeric_plot_info) + + for i in range(len(profile_report['variables'][col]['histogram']['counts'])): + numeric_plot_info = {} + numeric_plot_info['column'] = col + numeric_plot_info['value'] = profile_report['variables'][col]['histogram']['counts'][i] + numeric_plot_info['type'] = 'counts' + numeric_plot_infos.append(numeric_plot_info) + + cpt = 0 + for top in profile_report['variables'][col]['value_counts_without_nan']: + if (cpt < 10): + top_info = {} + top_info['column'] = col + top_info['value'] = top + top_info['nb'] = profile_report['variables'][col]['value_counts_without_nan'][top] + top_infos.append(top_info) + cpt = cpt + 1 + + df_to_sql(columns_infos, conn, 'columns_infos') + df_to_sql(categorical_infos, conn, 'categorical_infos') + df_to_sql(top_infos, conn, 'top_infos') + df_to_sql(numeric_infos, conn, 'numeric_infos') + df_to_sql(numeric_plot_infos, conn, 'numeric_plot_infos') + + conn.commit() diff --git a/csvapi/webservice.py b/csvapi/webservice.py index 7f10811..85a0ba7 100644 --- a/csvapi/webservice.py +++ b/csvapi/webservice.py @@ -3,7 +3,7 @@ from quart import Quart, jsonify from quart_cors import cors -from quart.exceptions import NotFound +from werkzeug.exceptions import NotFound from csvapi.errors import APIError from csvapi.tableview import TableView diff --git a/poetry.lock b/poetry.lock index 721ec27..f9a3b15 100644 --- a/poetry.lock +++ b/poetry.lock @@ -56,30 +56,31 @@ test = ["crate", "geojson", "nose (>=1.1.2)"] [[package]] name = "aiofiles" -version = "0.8.0" +version = "22.1.0" description = "File support for asyncio." category = "main" optional = false -python-versions = ">=3.6,<4.0" +python-versions = ">=3.7,<4.0" [[package]] name = "aiohttp" -version = "3.7.4.post0" +version = "3.8.1" description = "Async http client/server framework (asyncio)" category = "main" optional = false python-versions = ">=3.6" [package.dependencies] -async-timeout = ">=3.0,<4.0" +aiosignal = ">=1.1.2" +async-timeout = ">=4.0.0a3,<5.0" attrs = ">=17.3.0" -chardet = ">=2.0,<5.0" +charset-normalizer = ">=2.0,<3.0" +frozenlist = ">=1.1.1" multidict = ">=4.5,<7.0" -typing-extensions = ">=3.6.5" yarl = ">=1.0,<2.0" [package.extras] -speedups = ["aiodns", "brotlipy", "cchardet"] +speedups = ["aiodns", "brotli", "cchardet"] [[package]] name = "aioresponses" @@ -92,9 +93,20 @@ python-versions = "*" [package.dependencies] aiohttp = ">=2.0.0,<4.0.0" +[[package]] +name = "aiosignal" +version = "1.2.0" +description = "aiosignal: a list of registered asynchronous callbacks" +category = "main" +optional = false +python-versions = ">=3.6" + +[package.dependencies] +frozenlist = ">=1.1.0" + [[package]] name = "aiosqlite" -version = "0.16.1" +version = "0.17.0" description = "asyncio bridge to the standard sqlite3 module" category = "main" optional = false @@ -105,19 +117,11 @@ typing_extensions = ">=3.7.2" [[package]] name = "async-timeout" -version = "3.0.1" +version = "4.0.2" description = "Timeout context manager for asyncio programs" category = "main" optional = false -python-versions = ">=3.5.3" - -[[package]] -name = "atomicwrites" -version = "1.4.1" -description = "Atomic file writes." -category = "dev" -optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +python-versions = ">=3.6" [[package]] name = "attrs" @@ -154,27 +158,27 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" [[package]] name = "boto3" -version = "1.21.46" +version = "1.24.66" description = "The AWS SDK for Python" category = "main" optional = false -python-versions = ">= 3.6" +python-versions = ">= 3.7" [package.dependencies] -botocore = ">=1.24.46,<1.25.0" +botocore = ">=1.27.66,<1.28.0" jmespath = ">=0.7.1,<2.0.0" -s3transfer = ">=0.5.0,<0.6.0" +s3transfer = ">=0.6.0,<0.7.0" [package.extras] crt = ["botocore[crt] (>=1.21.0,<2.0a0)"] [[package]] name = "botocore" -version = "1.24.46" +version = "1.27.66" description = "Low-level, data-driven core of boto 3." category = "main" optional = false -python-versions = ">= 3.6" +python-versions = ">= 3.7" [package.dependencies] jmespath = ">=0.7.1,<2.0.0" @@ -182,7 +186,7 @@ python-dateutil = ">=2.1,<3.0.0" urllib3 = ">=1.25.4,<1.27" [package.extras] -crt = ["awscrt (==0.13.8)"] +crt = ["awscrt (==0.14.0)"] [[package]] name = "cchardet" @@ -202,19 +206,19 @@ python-versions = ">=3.6" [[package]] name = "chardet" -version = "4.0.0" -description = "Universal encoding detector for Python 2 and 3" +version = "5.0.0" +description = "Universal encoding detector for Python 3" category = "main" optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" +python-versions = ">=3.6" [[package]] name = "charset-normalizer" -version = "2.0.12" +version = "2.1.1" description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." category = "main" optional = false -python-versions = ">=3.5.0" +python-versions = ">=3.6.0" [package.extras] unicode_backport = ["unicodedata2"] @@ -257,9 +261,30 @@ category = "dev" optional = false python-versions = ">=3.7" +[package.dependencies] +tomli = {version = "*", optional = true, markers = "python_full_version <= \"3.11.0a6\" and extra == \"toml\""} + [package.extras] toml = ["tomli"] +[[package]] +name = "csv-detective" +version = "0.4.5" +description = "Detect CSV column content" +category = "main" +optional = false +python-versions = "*" + +[package.dependencies] +boto3 = ">=1.21.21" +cchardet = "*" +chardet = ">=3.0" +pandas = ">=0.20" +unidecode = ">=0.4" + +[package.extras] +test = ["nose"] + [[package]] name = "cycler" version = "0.11.0" @@ -319,6 +344,14 @@ ufo = ["fs (>=2.2.0,<3)"] unicode = ["unicodedata2 (>=14.0.0)"] woff = ["brotli (>=1.0.1)", "brotlicffi (>=0.8.0)", "zopfli (>=0.1.4)"] +[[package]] +name = "frozenlist" +version = "1.3.1" +description = "A list-like structure which implements collections.abc.MutableSequence" +category = "main" +optional = false +python-versions = ">=3.7" + [[package]] name = "future" version = "0.18.2" @@ -376,7 +409,7 @@ python-versions = "*" [[package]] name = "hypercorn" -version = "0.13.2" +version = "0.14.3" description = "A ASGI Server based on Hyper libraries and inspired by Gunicorn" category = "main" optional = false @@ -390,6 +423,7 @@ toml = "*" wsproto = ">=0.14.0" [package.extras] +docs = ["pydata-sphinx-theme"] h3 = ["aioquic (>=0.9.0,<1.0)"] trio = ["trio (>=0.11.0)"] uvloop = ["uvloop"] @@ -412,7 +446,7 @@ python-versions = ">=3.5" [[package]] name = "imagehash" -version = "4.2.1" +version = "4.3.0" description = "Image Hashing library" category = "main" optional = false @@ -423,7 +457,22 @@ numpy = "*" pillow = "*" PyWavelets = "*" scipy = "*" -six = "*" + +[[package]] +name = "importlib-metadata" +version = "4.12.0" +description = "Read metadata from Python packages" +category = "main" +optional = false +python-versions = ">=3.7" + +[package.dependencies] +zipp = ">=0.5" + +[package.extras] +docs = ["jaraco.packaging (>=9)", "rst.linker (>=1.9)", "sphinx"] +perf = ["ipython"] +testing = ["flufl.flake8", "importlib-resources (>=1.3)", "packaging", "pyfakefs", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-flake8", "pytest-mypy (>=0.9.1)", "pytest-perf (>=0.9.2)"] [[package]] name = "iniconfig" @@ -454,11 +503,11 @@ python-versions = ">=3.7" [[package]] name = "jinja2" -version = "3.0.3" +version = "3.1.2" description = "A very fast and expressive template engine." category = "main" optional = false -python-versions = ">=3.6" +python-versions = ">=3.7" [package.dependencies] MarkupSafe = ">=2.0" @@ -635,7 +684,7 @@ pyparsing = ">=2.0.2,<3.0.5 || >3.0.5" [[package]] name = "pandas" -version = "1.4.3" +version = "1.4.4" description = "Powerful data structures for data analysis, time series, and statistics" category = "main" optional = false @@ -764,14 +813,14 @@ python-versions = ">=3.6" [[package]] name = "pydantic" -version = "1.9.2" +version = "1.10.2" description = "Data validation and settings management using python type hints" category = "main" optional = false -python-versions = ">=3.6.1" +python-versions = ">=3.7" [package.dependencies] -typing-extensions = ">=3.7.4.3" +typing-extensions = ">=4.1.0" [package.extras] dotenv = ["python-dotenv (>=0.10.4)"] @@ -798,14 +847,13 @@ diagrams = ["jinja2", "railroad-diagrams"] [[package]] name = "pytest" -version = "7.1.2" +version = "7.1.3" description = "pytest: simple powerful testing with Python" category = "dev" optional = false python-versions = ">=3.7" [package.dependencies] -atomicwrites = {version = ">=1.0", markers = "sys_platform == \"win32\""} attrs = ">=19.2.0" colorama = {version = "*", markers = "sys_platform == \"win32\""} iniconfig = "*" @@ -833,15 +881,18 @@ testing = ["coverage (>=6.2)", "flaky (>=3.5.0)", "hypothesis (>=5.7.1)", "mypy [[package]] name = "pytest-cov" -version = "2.5.1" +version = "3.0.0" description = "Pytest plugin for measuring coverage." category = "dev" optional = false -python-versions = "*" +python-versions = ">=3.6" [package.dependencies] -coverage = ">=3.7.1" -pytest = ">=2.6.0" +coverage = {version = ">=5.2.1", extras = ["toml"]} +pytest = ">=4.6" + +[package.extras] +testing = ["fields", "hunter", "process-tests", "pytest-xdist", "six", "virtualenv"] [[package]] name = "python-dateutil" @@ -870,7 +921,7 @@ unidecode = ["Unidecode (>=1.1.1)"] [[package]] name = "python-stdnum" -version = "1.15" +version = "1.17" description = "Python module to handle standardized numbers and codes" category = "main" optional = false @@ -918,61 +969,63 @@ python-versions = ">=3.6" [[package]] name = "quart" -version = "0.14.1" +version = "0.18.0" description = "A Python ASGI web microframework with the same API as Flask" category = "main" optional = false -python-versions = ">=3.7.0" +python-versions = ">=3.7" [package.dependencies] aiofiles = "*" blinker = "*" click = "*" -hypercorn = ">=0.7.0" +hypercorn = ">=0.11.2" +importlib_metadata = {version = "*", markers = "python_version < \"3.10\""} itsdangerous = "*" jinja2 = "*" -toml = "*" -werkzeug = ">=1.0.0" +markupsafe = "*" +werkzeug = ">=2.2.0" [package.extras] +docs = ["pydata-sphinx-theme"] dotenv = ["python-dotenv"] [[package]] name = "quart-cors" -version = "0.3.0" +version = "0.5.0" description = "A Quart extension to provide Cross Origin Resource Sharing, access control, support." category = "main" optional = false python-versions = ">=3.7.0" [package.dependencies] -Quart = ">=0.11.1" +Quart = ">=0.15" [[package]] name = "requests" -version = "2.27.1" +version = "2.28.1" description = "Python HTTP for Humans." category = "main" optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*" +python-versions = ">=3.7, <4" [package.dependencies] certifi = ">=2017.4.17" -charset-normalizer = {version = ">=2.0.0,<2.1.0", markers = "python_version >= \"3\""} -idna = {version = ">=2.5,<4", markers = "python_version >= \"3\""} +charset-normalizer = ">=2,<3" +idna = ">=2.5,<4" urllib3 = ">=1.21.1,<1.27" [package.extras] -socks = ["PySocks (>=1.5.6,!=1.5.7)", "win-inet-pton"] -use_chardet_on_py3 = ["chardet (>=3.0.2,<5)"] +socks = ["PySocks (>=1.5.6,!=1.5.7)"] +use_chardet_on_py3 = ["chardet (>=3.0.2,<6)"] [[package]] name = "s3transfer" -version = "0.5.2" +version = "0.6.0" description = "An Amazon S3 Transfer Manager" category = "main" optional = false -python-versions = ">= 3.6" +python-versions = ">= 3.7" [package.dependencies] botocore = ">=1.12.36,<2.0a.0" @@ -993,21 +1046,23 @@ numpy = ">=1.16.5" [[package]] name = "seaborn" -version = "0.11.2" -description = "seaborn: statistical data visualization" +version = "0.12.0" +description = "Statistical data visualization" category = "main" optional = false -python-versions = ">=3.6" +python-versions = ">=3.7" [package.dependencies] -matplotlib = ">=2.2" -numpy = ">=1.15" -pandas = ">=0.23" -scipy = ">=1.0" +matplotlib = ">=3.1" +numpy = ">=1.17" +pandas = ">=0.25" + +[package.extras] +stats = ["scipy (>=1.3)", "statsmodels (>=0.10)"] [[package]] name = "sentry-sdk" -version = "1.0.0" +version = "1.9.8" description = "Python client for Sentry (https://sentry.io)" category = "main" optional = false @@ -1015,7 +1070,7 @@ python-versions = "*" [package.dependencies] certifi = "*" -urllib3 = ">=1.10.0" +urllib3 = {version = ">=1.26.11", markers = "python_version >= \"3.6\""} [package.extras] aiohttp = ["aiohttp (>=3.5)"] @@ -1025,12 +1080,16 @@ celery = ["celery (>=3)"] chalice = ["chalice (>=1.16.0)"] django = ["django (>=1.8)"] falcon = ["falcon (>=1.4)"] +fastapi = ["fastapi (>=0.79.0)"] flask = ["blinker (>=1.1)", "flask (>=0.11)"] +httpx = ["httpx (>=0.16.0)"] pure_eval = ["asttokens", "executing", "pure-eval"] pyspark = ["pyspark (>=2.4.4)"] +quart = ["blinker (>=1.1)", "quart (>=0.16.1)"] rq = ["rq (>=0.6)"] sanic = ["sanic (>=0.8)"] sqlalchemy = ["sqlalchemy (>=1.2)"] +starlette = ["starlette (>=0.19.1)"] tornado = ["tornado (>=5)"] [[package]] @@ -1123,7 +1182,7 @@ python-versions = ">=3.7" [[package]] name = "tqdm" -version = "4.64.0" +version = "4.64.1" description = "Fast, Extensible Progress Meter" category = "main" optional = false @@ -1157,6 +1216,14 @@ python-versions = "*" [package.dependencies] kafka-python = "2.0.2" +[[package]] +name = "unidecode" +version = "1.3.4" +description = "ASCII transliterations of Unicode text" +category = "main" +optional = false +python-versions = ">=3.5" + [[package]] name = "urllib3" version = "1.26.12" @@ -1172,7 +1239,7 @@ socks = ["PySocks (>=1.5.6,!=1.5.7,<2.0)"] [[package]] name = "validators" -version = "0.18.2" +version = "0.20.0" description = "Python Data Validation for Humans™." category = "main" optional = false @@ -1180,7 +1247,6 @@ python-versions = ">=3.4" [package.dependencies] decorator = ">=3.4.0" -six = ">=1.4.0" [package.extras] test = ["flake8 (>=2.4.0)", "isort (>=4.2.2)", "pytest (>=2.2.3)"] @@ -1213,14 +1279,16 @@ type_image_path = ["imagehash", "pillow"] [[package]] name = "werkzeug" -version = "1.0.1" +version = "2.2.2" description = "The comprehensive WSGI web application library." category = "main" optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" +python-versions = ">=3.7" + +[package.dependencies] +MarkupSafe = ">=2.1.1" [package.extras] -dev = ["coverage", "pallets-sphinx-themes", "pytest", "pytest-timeout", "sphinx", "sphinx-issues", "tox"] watchdog = ["watchdog"] [[package]] @@ -1259,10 +1327,22 @@ python-versions = ">=3.7" idna = ">=2.0" multidict = ">=4.0" +[[package]] +name = "zipp" +version = "3.8.1" +description = "Backport of pathlib-compatible object wrapper for zip files" +category = "main" +optional = false +python-versions = ">=3.7" + +[package.extras] +docs = ["jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx"] +testing = ["func-timeout", "jaraco.itertools", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-flake8", "pytest-mypy (>=0.9.1)"] + [metadata] lock-version = "1.1" python-versions = ">=3.9,<4" -content-hash = "423e8b1cadacc743d465e6bf72e580281db552618c90638948a6561a7d07c76d" +content-hash = "b3c29ba4e3765561abdbd6d869a67341c88587eebcc6fb6b9d96cca06de9bbc4" [metadata.files] agate = [ @@ -1278,62 +1358,98 @@ agate-sql = [ {file = "agate_sql-0.5.8-py2.py3-none-any.whl", hash = "sha256:aae3d1014c3396cb0c21a21f0e41b514128bc0479b9a1627d020611f470199c3"}, ] aiofiles = [ - {file = "aiofiles-0.8.0-py3-none-any.whl", hash = "sha256:7a973fc22b29e9962d0897805ace5856e6a566ab1f0c8e5c91ff6c866519c937"}, - {file = "aiofiles-0.8.0.tar.gz", hash = "sha256:8334f23235248a3b2e83b2c3a78a22674f39969b96397126cc93664d9a901e59"}, + {file = "aiofiles-22.1.0-py3-none-any.whl", hash = "sha256:1142fa8e80dbae46bb6339573ad4c8c0841358f79c6eb50a493dceca14621bad"}, + {file = "aiofiles-22.1.0.tar.gz", hash = "sha256:9107f1ca0b2a5553987a94a3c9959fe5b491fdf731389aa5b7b1bd0733e32de6"}, ] aiohttp = [ - {file = "aiohttp-3.7.4.post0-cp36-cp36m-macosx_10_14_x86_64.whl", hash = "sha256:3cf75f7cdc2397ed4442594b935a11ed5569961333d49b7539ea741be2cc79d5"}, - {file = "aiohttp-3.7.4.post0-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:4b302b45040890cea949ad092479e01ba25911a15e648429c7c5aae9650c67a8"}, - {file = "aiohttp-3.7.4.post0-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:fe60131d21b31fd1a14bd43e6bb88256f69dfc3188b3a89d736d6c71ed43ec95"}, - {file = "aiohttp-3.7.4.post0-cp36-cp36m-manylinux2014_i686.whl", hash = "sha256:393f389841e8f2dfc86f774ad22f00923fdee66d238af89b70ea314c4aefd290"}, - {file = "aiohttp-3.7.4.post0-cp36-cp36m-manylinux2014_ppc64le.whl", hash = "sha256:c6e9dcb4cb338d91a73f178d866d051efe7c62a7166653a91e7d9fb18274058f"}, - {file = "aiohttp-3.7.4.post0-cp36-cp36m-manylinux2014_s390x.whl", hash = "sha256:5df68496d19f849921f05f14f31bd6ef53ad4b00245da3195048c69934521809"}, - {file = "aiohttp-3.7.4.post0-cp36-cp36m-manylinux2014_x86_64.whl", hash = "sha256:0563c1b3826945eecd62186f3f5c7d31abb7391fedc893b7e2b26303b5a9f3fe"}, - {file = "aiohttp-3.7.4.post0-cp36-cp36m-win32.whl", hash = "sha256:3d78619672183be860b96ed96f533046ec97ca067fd46ac1f6a09cd9b7484287"}, - {file = "aiohttp-3.7.4.post0-cp36-cp36m-win_amd64.whl", hash = "sha256:f705e12750171c0ab4ef2a3c76b9a4024a62c4103e3a55dd6f99265b9bc6fcfc"}, - {file = "aiohttp-3.7.4.post0-cp37-cp37m-macosx_10_14_x86_64.whl", hash = "sha256:230a8f7e24298dea47659251abc0fd8b3c4e38a664c59d4b89cca7f6c09c9e87"}, - {file = "aiohttp-3.7.4.post0-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:2e19413bf84934d651344783c9f5e22dee452e251cfd220ebadbed2d9931dbf0"}, - {file = "aiohttp-3.7.4.post0-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:e4b2b334e68b18ac9817d828ba44d8fcb391f6acb398bcc5062b14b2cbeac970"}, - {file = "aiohttp-3.7.4.post0-cp37-cp37m-manylinux2014_i686.whl", hash = "sha256:d012ad7911653a906425d8473a1465caa9f8dea7fcf07b6d870397b774ea7c0f"}, - {file = "aiohttp-3.7.4.post0-cp37-cp37m-manylinux2014_ppc64le.whl", hash = "sha256:40eced07f07a9e60e825554a31f923e8d3997cfc7fb31dbc1328c70826e04cde"}, - {file = "aiohttp-3.7.4.post0-cp37-cp37m-manylinux2014_s390x.whl", hash = "sha256:209b4a8ee987eccc91e2bd3ac36adee0e53a5970b8ac52c273f7f8fd4872c94c"}, - {file = "aiohttp-3.7.4.post0-cp37-cp37m-manylinux2014_x86_64.whl", hash = "sha256:14762875b22d0055f05d12abc7f7d61d5fd4fe4642ce1a249abdf8c700bf1fd8"}, - {file = "aiohttp-3.7.4.post0-cp37-cp37m-win32.whl", hash = "sha256:7615dab56bb07bff74bc865307aeb89a8bfd9941d2ef9d817b9436da3a0ea54f"}, - {file = "aiohttp-3.7.4.post0-cp37-cp37m-win_amd64.whl", hash = "sha256:d9e13b33afd39ddeb377eff2c1c4f00544e191e1d1dee5b6c51ddee8ea6f0cf5"}, - {file = "aiohttp-3.7.4.post0-cp38-cp38-macosx_10_14_x86_64.whl", hash = "sha256:547da6cacac20666422d4882cfcd51298d45f7ccb60a04ec27424d2f36ba3eaf"}, - {file = "aiohttp-3.7.4.post0-cp38-cp38-manylinux1_i686.whl", hash = "sha256:af9aa9ef5ba1fd5b8c948bb11f44891968ab30356d65fd0cc6707d989cd521df"}, - {file = "aiohttp-3.7.4.post0-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:64322071e046020e8797117b3658b9c2f80e3267daec409b350b6a7a05041213"}, - {file = "aiohttp-3.7.4.post0-cp38-cp38-manylinux2014_i686.whl", hash = "sha256:bb437315738aa441251214dad17428cafda9cdc9729499f1d6001748e1d432f4"}, - {file = "aiohttp-3.7.4.post0-cp38-cp38-manylinux2014_ppc64le.whl", hash = "sha256:e54962802d4b8b18b6207d4a927032826af39395a3bd9196a5af43fc4e60b009"}, - {file = "aiohttp-3.7.4.post0-cp38-cp38-manylinux2014_s390x.whl", hash = "sha256:a00bb73540af068ca7390e636c01cbc4f644961896fa9363154ff43fd37af2f5"}, - {file = "aiohttp-3.7.4.post0-cp38-cp38-manylinux2014_x86_64.whl", hash = "sha256:79ebfc238612123a713a457d92afb4096e2148be17df6c50fb9bf7a81c2f8013"}, - {file = "aiohttp-3.7.4.post0-cp38-cp38-win32.whl", hash = "sha256:515dfef7f869a0feb2afee66b957cc7bbe9ad0cdee45aec7fdc623f4ecd4fb16"}, - {file = "aiohttp-3.7.4.post0-cp38-cp38-win_amd64.whl", hash = "sha256:114b281e4d68302a324dd33abb04778e8557d88947875cbf4e842c2c01a030c5"}, - {file = "aiohttp-3.7.4.post0-cp39-cp39-macosx_10_14_x86_64.whl", hash = "sha256:7b18b97cf8ee5452fa5f4e3af95d01d84d86d32c5e2bfa260cf041749d66360b"}, - {file = "aiohttp-3.7.4.post0-cp39-cp39-manylinux1_i686.whl", hash = "sha256:15492a6368d985b76a2a5fdd2166cddfea5d24e69eefed4630cbaae5c81d89bd"}, - {file = "aiohttp-3.7.4.post0-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:bdb230b4943891321e06fc7def63c7aace16095be7d9cf3b1e01be2f10fba439"}, - {file = "aiohttp-3.7.4.post0-cp39-cp39-manylinux2014_i686.whl", hash = "sha256:cffe3ab27871bc3ea47df5d8f7013945712c46a3cc5a95b6bee15887f1675c22"}, - {file = "aiohttp-3.7.4.post0-cp39-cp39-manylinux2014_ppc64le.whl", hash = "sha256:f881853d2643a29e643609da57b96d5f9c9b93f62429dcc1cbb413c7d07f0e1a"}, - {file = "aiohttp-3.7.4.post0-cp39-cp39-manylinux2014_s390x.whl", hash = "sha256:a5ca29ee66f8343ed336816c553e82d6cade48a3ad702b9ffa6125d187e2dedb"}, - {file = "aiohttp-3.7.4.post0-cp39-cp39-manylinux2014_x86_64.whl", hash = "sha256:17c073de315745a1510393a96e680d20af8e67e324f70b42accbd4cb3315c9fb"}, - {file = "aiohttp-3.7.4.post0-cp39-cp39-win32.whl", hash = "sha256:932bb1ea39a54e9ea27fc9232163059a0b8855256f4052e776357ad9add6f1c9"}, - {file = "aiohttp-3.7.4.post0-cp39-cp39-win_amd64.whl", hash = "sha256:02f46fc0e3c5ac58b80d4d56eb0a7c7d97fcef69ace9326289fb9f1955e65cfe"}, - {file = "aiohttp-3.7.4.post0.tar.gz", hash = "sha256:493d3299ebe5f5a7c66b9819eacdcfbbaaf1a8e84911ddffcdc48888497afecf"}, + {file = "aiohttp-3.8.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:1ed0b6477896559f17b9eaeb6d38e07f7f9ffe40b9f0f9627ae8b9926ae260a8"}, + {file = "aiohttp-3.8.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:7dadf3c307b31e0e61689cbf9e06be7a867c563d5a63ce9dca578f956609abf8"}, + {file = "aiohttp-3.8.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:a79004bb58748f31ae1cbe9fa891054baaa46fb106c2dc7af9f8e3304dc30316"}, + {file = "aiohttp-3.8.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:12de6add4038df8f72fac606dff775791a60f113a725c960f2bab01d8b8e6b15"}, + {file = "aiohttp-3.8.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6f0d5f33feb5f69ddd57a4a4bd3d56c719a141080b445cbf18f238973c5c9923"}, + {file = "aiohttp-3.8.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:eaba923151d9deea315be1f3e2b31cc39a6d1d2f682f942905951f4e40200922"}, + {file = "aiohttp-3.8.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:099ebd2c37ac74cce10a3527d2b49af80243e2a4fa39e7bce41617fbc35fa3c1"}, + {file = "aiohttp-3.8.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:2e5d962cf7e1d426aa0e528a7e198658cdc8aa4fe87f781d039ad75dcd52c516"}, + {file = "aiohttp-3.8.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:fa0ffcace9b3aa34d205d8130f7873fcfefcb6a4dd3dd705b0dab69af6712642"}, + {file = "aiohttp-3.8.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:61bfc23df345d8c9716d03717c2ed5e27374e0fe6f659ea64edcd27b4b044cf7"}, + {file = "aiohttp-3.8.1-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:31560d268ff62143e92423ef183680b9829b1b482c011713ae941997921eebc8"}, + {file = "aiohttp-3.8.1-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:01d7bdb774a9acc838e6b8f1d114f45303841b89b95984cbb7d80ea41172a9e3"}, + {file = "aiohttp-3.8.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:97ef77eb6b044134c0b3a96e16abcb05ecce892965a2124c566af0fd60f717e2"}, + {file = "aiohttp-3.8.1-cp310-cp310-win32.whl", hash = "sha256:c2aef4703f1f2ddc6df17519885dbfa3514929149d3ff900b73f45998f2532fa"}, + {file = "aiohttp-3.8.1-cp310-cp310-win_amd64.whl", hash = "sha256:713ac174a629d39b7c6a3aa757b337599798da4c1157114a314e4e391cd28e32"}, + {file = "aiohttp-3.8.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:473d93d4450880fe278696549f2e7aed8cd23708c3c1997981464475f32137db"}, + {file = "aiohttp-3.8.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:99b5eeae8e019e7aad8af8bb314fb908dd2e028b3cdaad87ec05095394cce632"}, + {file = "aiohttp-3.8.1-cp36-cp36m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3af642b43ce56c24d063325dd2cf20ee012d2b9ba4c3c008755a301aaea720ad"}, + {file = "aiohttp-3.8.1-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c3630c3ef435c0a7c549ba170a0633a56e92629aeed0e707fec832dee313fb7a"}, + {file = "aiohttp-3.8.1-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:4a4a4e30bf1edcad13fb0804300557aedd07a92cabc74382fdd0ba6ca2661091"}, + {file = "aiohttp-3.8.1-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:6f8b01295e26c68b3a1b90efb7a89029110d3a4139270b24fda961893216c440"}, + {file = "aiohttp-3.8.1-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:a25fa703a527158aaf10dafd956f7d42ac6d30ec80e9a70846253dd13e2f067b"}, + {file = "aiohttp-3.8.1-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:5bfde62d1d2641a1f5173b8c8c2d96ceb4854f54a44c23102e2ccc7e02f003ec"}, + {file = "aiohttp-3.8.1-cp36-cp36m-musllinux_1_1_ppc64le.whl", hash = "sha256:51467000f3647d519272392f484126aa716f747859794ac9924a7aafa86cd411"}, + {file = "aiohttp-3.8.1-cp36-cp36m-musllinux_1_1_s390x.whl", hash = "sha256:03a6d5349c9ee8f79ab3ff3694d6ce1cfc3ced1c9d36200cb8f08ba06bd3b782"}, + {file = "aiohttp-3.8.1-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:102e487eeb82afac440581e5d7f8f44560b36cf0bdd11abc51a46c1cd88914d4"}, + {file = "aiohttp-3.8.1-cp36-cp36m-win32.whl", hash = "sha256:4aed991a28ea3ce320dc8ce655875e1e00a11bdd29fe9444dd4f88c30d558602"}, + {file = "aiohttp-3.8.1-cp36-cp36m-win_amd64.whl", hash = "sha256:b0e20cddbd676ab8a64c774fefa0ad787cc506afd844de95da56060348021e96"}, + {file = "aiohttp-3.8.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:37951ad2f4a6df6506750a23f7cbabad24c73c65f23f72e95897bb2cecbae676"}, + {file = "aiohttp-3.8.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5c23b1ad869653bc818e972b7a3a79852d0e494e9ab7e1a701a3decc49c20d51"}, + {file = "aiohttp-3.8.1-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:15b09b06dae900777833fe7fc4b4aa426556ce95847a3e8d7548e2d19e34edb8"}, + {file = "aiohttp-3.8.1-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:477c3ea0ba410b2b56b7efb072c36fa91b1e6fc331761798fa3f28bb224830dd"}, + {file = "aiohttp-3.8.1-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:2f2f69dca064926e79997f45b2f34e202b320fd3782f17a91941f7eb85502ee2"}, + {file = "aiohttp-3.8.1-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:ef9612483cb35171d51d9173647eed5d0069eaa2ee812793a75373447d487aa4"}, + {file = "aiohttp-3.8.1-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:6d69f36d445c45cda7b3b26afef2fc34ef5ac0cdc75584a87ef307ee3c8c6d00"}, + {file = "aiohttp-3.8.1-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:55c3d1072704d27401c92339144d199d9de7b52627f724a949fc7d5fc56d8b93"}, + {file = "aiohttp-3.8.1-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:b9d00268fcb9f66fbcc7cd9fe423741d90c75ee029a1d15c09b22d23253c0a44"}, + {file = "aiohttp-3.8.1-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:07b05cd3305e8a73112103c834e91cd27ce5b4bd07850c4b4dbd1877d3f45be7"}, + {file = "aiohttp-3.8.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:c34dc4958b232ef6188c4318cb7b2c2d80521c9a56c52449f8f93ab7bc2a8a1c"}, + {file = "aiohttp-3.8.1-cp37-cp37m-win32.whl", hash = "sha256:d2f9b69293c33aaa53d923032fe227feac867f81682f002ce33ffae978f0a9a9"}, + {file = "aiohttp-3.8.1-cp37-cp37m-win_amd64.whl", hash = "sha256:6ae828d3a003f03ae31915c31fa684b9890ea44c9c989056fea96e3d12a9fa17"}, + {file = "aiohttp-3.8.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:0c7ebbbde809ff4e970824b2b6cb7e4222be6b95a296e46c03cf050878fc1785"}, + {file = "aiohttp-3.8.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:8b7ef7cbd4fec9a1e811a5de813311ed4f7ac7d93e0fda233c9b3e1428f7dd7b"}, + {file = "aiohttp-3.8.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:c3d6a4d0619e09dcd61021debf7059955c2004fa29f48788a3dfaf9c9901a7cd"}, + {file = "aiohttp-3.8.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:718626a174e7e467f0558954f94af117b7d4695d48eb980146016afa4b580b2e"}, + {file = "aiohttp-3.8.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:589c72667a5febd36f1315aa6e5f56dd4aa4862df295cb51c769d16142ddd7cd"}, + {file = "aiohttp-3.8.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2ed076098b171573161eb146afcb9129b5ff63308960aeca4b676d9d3c35e700"}, + {file = "aiohttp-3.8.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:086f92daf51a032d062ec5f58af5ca6a44d082c35299c96376a41cbb33034675"}, + {file = "aiohttp-3.8.1-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:11691cf4dc5b94236ccc609b70fec991234e7ef8d4c02dd0c9668d1e486f5abf"}, + {file = "aiohttp-3.8.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:31d1e1c0dbf19ebccbfd62eff461518dcb1e307b195e93bba60c965a4dcf1ba0"}, + {file = "aiohttp-3.8.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:11a67c0d562e07067c4e86bffc1553f2cf5b664d6111c894671b2b8712f3aba5"}, + {file = "aiohttp-3.8.1-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:bb01ba6b0d3f6c68b89fce7305080145d4877ad3acaed424bae4d4ee75faa950"}, + {file = "aiohttp-3.8.1-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:44db35a9e15d6fe5c40d74952e803b1d96e964f683b5a78c3cc64eb177878155"}, + {file = "aiohttp-3.8.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:844a9b460871ee0a0b0b68a64890dae9c415e513db0f4a7e3cab41a0f2fedf33"}, + {file = "aiohttp-3.8.1-cp38-cp38-win32.whl", hash = "sha256:7d08744e9bae2ca9c382581f7dce1273fe3c9bae94ff572c3626e8da5b193c6a"}, + {file = "aiohttp-3.8.1-cp38-cp38-win_amd64.whl", hash = "sha256:04d48b8ce6ab3cf2097b1855e1505181bdd05586ca275f2505514a6e274e8e75"}, + {file = "aiohttp-3.8.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:f5315a2eb0239185af1bddb1abf472d877fede3cc8d143c6cddad37678293237"}, + {file = "aiohttp-3.8.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:a996d01ca39b8dfe77440f3cd600825d05841088fd6bc0144cc6c2ec14cc5f74"}, + {file = "aiohttp-3.8.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:13487abd2f761d4be7c8ff9080de2671e53fff69711d46de703c310c4c9317ca"}, + {file = "aiohttp-3.8.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ea302f34477fda3f85560a06d9ebdc7fa41e82420e892fc50b577e35fc6a50b2"}, + {file = "aiohttp-3.8.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a2f635ce61a89c5732537a7896b6319a8fcfa23ba09bec36e1b1ac0ab31270d2"}, + {file = "aiohttp-3.8.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e999f2d0e12eea01caeecb17b653f3713d758f6dcc770417cf29ef08d3931421"}, + {file = "aiohttp-3.8.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:0770e2806a30e744b4e21c9d73b7bee18a1cfa3c47991ee2e5a65b887c49d5cf"}, + {file = "aiohttp-3.8.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:d15367ce87c8e9e09b0f989bfd72dc641bcd04ba091c68cd305312d00962addd"}, + {file = "aiohttp-3.8.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:6c7cefb4b0640703eb1069835c02486669312bf2f12b48a748e0a7756d0de33d"}, + {file = "aiohttp-3.8.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:71927042ed6365a09a98a6377501af5c9f0a4d38083652bcd2281a06a5976724"}, + {file = "aiohttp-3.8.1-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:28d490af82bc6b7ce53ff31337a18a10498303fe66f701ab65ef27e143c3b0ef"}, + {file = "aiohttp-3.8.1-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:b6613280ccedf24354406caf785db748bebbddcf31408b20c0b48cb86af76866"}, + {file = "aiohttp-3.8.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:81e3d8c34c623ca4e36c46524a3530e99c0bc95ed068fd6e9b55cb721d408fb2"}, + {file = "aiohttp-3.8.1-cp39-cp39-win32.whl", hash = "sha256:7187a76598bdb895af0adbd2fb7474d7f6025d170bc0a1130242da817ce9e7d1"}, + {file = "aiohttp-3.8.1-cp39-cp39-win_amd64.whl", hash = "sha256:1c182cb873bc91b411e184dab7a2b664d4fea2743df0e4d57402f7f3fa644bac"}, + {file = "aiohttp-3.8.1.tar.gz", hash = "sha256:fc5471e1a54de15ef71c1bc6ebe80d4dc681ea600e68bfd1cbce40427f0b7578"}, ] aioresponses = [ {file = "aioresponses-0.7.3-py2.py3-none-any.whl", hash = "sha256:7b1897169062c92fa87d6ecc503ac566ac87fbfacb2504f8ca81c8035a2eb068"}, {file = "aioresponses-0.7.3.tar.gz", hash = "sha256:2c64ed5710ee8cb4e958c569184dad12f4c9cd5939135cb38f88c6a8261cceb3"}, ] +aiosignal = [ + {file = "aiosignal-1.2.0-py3-none-any.whl", hash = "sha256:26e62109036cd181df6e6ad646f91f0dcfd05fe16d0cb924138ff2ab75d64e3a"}, + {file = "aiosignal-1.2.0.tar.gz", hash = "sha256:78ed67db6c7b7ced4f98e495e572106d5c432a93e1ddd1bf475e1dc05f5b7df2"}, +] aiosqlite = [ - {file = "aiosqlite-0.16.1-py3-none-any.whl", hash = "sha256:1df802815bb1e08a26c06d5ea9df589bcb8eec56e5f3378103b0f9b223c6703c"}, - {file = "aiosqlite-0.16.1.tar.gz", hash = "sha256:2e915463164efa65b60fd1901aceca829b6090082f03082618afca6fb9c8fdf7"}, + {file = "aiosqlite-0.17.0-py3-none-any.whl", hash = "sha256:6c49dc6d3405929b1d08eeccc72306d3677503cc5e5e43771efc1e00232e8231"}, + {file = "aiosqlite-0.17.0.tar.gz", hash = "sha256:f0e6acc24bc4864149267ac82fb46dfb3be4455f99fe21df82609cc6e6baee51"}, ] async-timeout = [ - {file = "async-timeout-3.0.1.tar.gz", hash = "sha256:0c3c816a028d47f659d6ff5c745cb2acf1f966da1fe5c19c77a70282b25f4c5f"}, - {file = "async_timeout-3.0.1-py3-none-any.whl", hash = "sha256:4291ca197d287d274d0b6cb5d6f8f8f82d434ed288f962539ff18cc9012f9ea3"}, -] -atomicwrites = [ - {file = "atomicwrites-1.4.1.tar.gz", hash = "sha256:81b2c9071a49367a7f770170e5eec8cb66567cfbbc8c73d20ce5ca4a8d71cf11"}, + {file = "async-timeout-4.0.2.tar.gz", hash = "sha256:2163e1640ddb52b7a8c80d0a67a08587e5d245cc9c553a74a847056bc2976b15"}, + {file = "async_timeout-4.0.2-py3-none-any.whl", hash = "sha256:8ca1e4fcf50d07413d66d1a5e416e42cfdf5851c981d679a09851a6853383b3c"}, ] attrs = [ {file = "attrs-22.1.0-py2.py3-none-any.whl", hash = "sha256:86efa402f67bf2df34f51a335487cf46b1ec130d02b8d39fd248abfd30da551c"}, @@ -1348,12 +1464,12 @@ blinker = [ {file = "blinker-1.5.tar.gz", hash = "sha256:923e5e2f69c155f2cc42dafbbd70e16e3fde24d2d4aa2ab72fbe386238892462"}, ] boto3 = [ - {file = "boto3-1.21.46-py3-none-any.whl", hash = "sha256:3b13d727854aba9dea900b6c7fa134c52396869d842460d14fab8b85b69645f7"}, - {file = "boto3-1.21.46.tar.gz", hash = "sha256:9ac902076eac82112f4536cc2606a1f597a387dbc56b250575ac2d2c64c75e20"}, + {file = "boto3-1.24.66-py3-none-any.whl", hash = "sha256:8b866aa5f51f66663a3d7fb24450a6f7e84da1c996e0b0aa738d0e12c34fc92b"}, + {file = "boto3-1.24.66.tar.gz", hash = "sha256:60003d2b83268a303cf61b78a0b59ebe2abe87e2f21308b55a99f25fd9bca4db"}, ] botocore = [ - {file = "botocore-1.24.46-py3-none-any.whl", hash = "sha256:663d8f02b98641846eb959c54c840cc33264d5f2dee5b8fc09ee8adbef0f8dcf"}, - {file = "botocore-1.24.46.tar.gz", hash = "sha256:89a203bba3c8f2299287e48a9e112e2dbe478cf67eaac26716f0e7f176446146"}, + {file = "botocore-1.27.66-py3-none-any.whl", hash = "sha256:6293d1cb392a4779cf0a44055cae9ac0728809c14a11f2d91e679a00a9beae20"}, + {file = "botocore-1.27.66.tar.gz", hash = "sha256:6c8c8c82b38ba2353bd3bc071019ab44d8a160b9d17f3ab166f0ceaf1ca38c12"}, ] cchardet = [ {file = "cchardet-2.1.7-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:c6f70139aaf47ffb94d89db603af849b82efdf756f187cdd3e566e30976c519f"}, @@ -1391,12 +1507,12 @@ certifi = [ {file = "certifi-2022.6.15.tar.gz", hash = "sha256:84c85a9078b11105f04f3036a9482ae10e4621616db313fe045dd24743a0820d"}, ] chardet = [ - {file = "chardet-4.0.0-py2.py3-none-any.whl", hash = "sha256:f864054d66fd9118f2e67044ac8981a54775ec5b67aed0441892edb553d21da5"}, - {file = "chardet-4.0.0.tar.gz", hash = "sha256:0d6f53a15db4120f2b08c94f11e7d93d2c911ee118b6b30a04ec3ee8310179fa"}, + {file = "chardet-5.0.0-py3-none-any.whl", hash = "sha256:d3e64f022d254183001eccc5db4040520c0f23b1a3f33d6413e099eb7f126557"}, + {file = "chardet-5.0.0.tar.gz", hash = "sha256:0368df2bfd78b5fc20572bb4e9bb7fb53e2c094f60ae9993339e8671d0afb8aa"}, ] charset-normalizer = [ - {file = "charset-normalizer-2.0.12.tar.gz", hash = "sha256:2857e29ff0d34db842cd7ca3230549d1a697f96ee6d3fb071cfa6c7393832597"}, - {file = "charset_normalizer-2.0.12-py3-none-any.whl", hash = "sha256:6881edbebdb17b39b4eaaa821b438bf6eddffb4468cf344f09f89def34a8b1df"}, + {file = "charset-normalizer-2.1.1.tar.gz", hash = "sha256:5a3d016c7c547f69d6f81fb0db9449ce888b418b5b9952cc5e6e66843e9dd845"}, + {file = "charset_normalizer-2.1.1-py3-none-any.whl", hash = "sha256:83e9a75d1911279afd89352c68b45348559d1fc0506b054b346651b5e7fee29f"}, ] click = [ {file = "click-8.1.3-py3-none-any.whl", hash = "sha256:bb4d8133cb15a609f44e8213d9b391b0809795062913b383c62be0ee95b1db48"}, @@ -1461,6 +1577,10 @@ coverage = [ {file = "coverage-6.4.4-pp36.pp37.pp38-none-any.whl", hash = "sha256:f67cf9f406cf0d2f08a3515ce2db5b82625a7257f88aad87904674def6ddaec1"}, {file = "coverage-6.4.4.tar.gz", hash = "sha256:e16c45b726acb780e1e6f88b286d3c10b3914ab03438f32117c4aa52d7f30d58"}, ] +csv-detective = [ + {file = "csv_detective-0.4.5-py3-none-any.whl", hash = "sha256:4e5b987a397ff89fe0787010828b4ee86173a517f6765a3e14d95dc9358490bb"}, + {file = "csv_detective-0.4.5.tar.gz", hash = "sha256:c21ecd20a46cb1c4d79fac732c7fac8bc1acf849208be40c33eca47e9540bdd6"}, +] cycler = [ {file = "cycler-0.11.0-py3-none-any.whl", hash = "sha256:3a27e95f763a428a739d2add979fa7494c912a32c17c4c38c4d5f082cad165a3"}, {file = "cycler-0.11.0.tar.gz", hash = "sha256:9c87405839a19696e837b3b818fed3f5f69f16f1eec1a1ad77e043dcea9c772f"}, @@ -1481,6 +1601,67 @@ fonttools = [ {file = "fonttools-4.37.1-py3-none-any.whl", hash = "sha256:fff6b752e326c15756c819fe2fe7ceab69f96a1dbcfe8911d0941cdb49905007"}, {file = "fonttools-4.37.1.zip", hash = "sha256:4606e1a88ee1f6699d182fea9511bd9a8a915d913eab4584e5226da1180fcce7"}, ] +frozenlist = [ + {file = "frozenlist-1.3.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:5f271c93f001748fc26ddea409241312a75e13466b06c94798d1a341cf0e6989"}, + {file = "frozenlist-1.3.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:9c6ef8014b842f01f5d2b55315f1af5cbfde284eb184075c189fd657c2fd8204"}, + {file = "frozenlist-1.3.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:219a9676e2eae91cb5cc695a78b4cb43d8123e4160441d2b6ce8d2c70c60e2f3"}, + {file = "frozenlist-1.3.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b47d64cdd973aede3dd71a9364742c542587db214e63b7529fbb487ed67cddd9"}, + {file = "frozenlist-1.3.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2af6f7a4e93f5d08ee3f9152bce41a6015b5cf87546cb63872cc19b45476e98a"}, + {file = "frozenlist-1.3.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a718b427ff781c4f4e975525edb092ee2cdef6a9e7bc49e15063b088961806f8"}, + {file = "frozenlist-1.3.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c56c299602c70bc1bb5d1e75f7d8c007ca40c9d7aebaf6e4ba52925d88ef826d"}, + {file = "frozenlist-1.3.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:717470bfafbb9d9be624da7780c4296aa7935294bd43a075139c3d55659038ca"}, + {file = "frozenlist-1.3.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:31b44f1feb3630146cffe56344704b730c33e042ffc78d21f2125a6a91168131"}, + {file = "frozenlist-1.3.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:c3b31180b82c519b8926e629bf9f19952c743e089c41380ddca5db556817b221"}, + {file = "frozenlist-1.3.1-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:d82bed73544e91fb081ab93e3725e45dd8515c675c0e9926b4e1f420a93a6ab9"}, + {file = "frozenlist-1.3.1-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:49459f193324fbd6413e8e03bd65789e5198a9fa3095e03f3620dee2f2dabff2"}, + {file = "frozenlist-1.3.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:94e680aeedc7fd3b892b6fa8395b7b7cc4b344046c065ed4e7a1e390084e8cb5"}, + {file = "frozenlist-1.3.1-cp310-cp310-win32.whl", hash = "sha256:fabb953ab913dadc1ff9dcc3a7a7d3dc6a92efab3a0373989b8063347f8705be"}, + {file = "frozenlist-1.3.1-cp310-cp310-win_amd64.whl", hash = "sha256:eee0c5ecb58296580fc495ac99b003f64f82a74f9576a244d04978a7e97166db"}, + {file = "frozenlist-1.3.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:0bc75692fb3770cf2b5856a6c2c9de967ca744863c5e89595df64e252e4b3944"}, + {file = "frozenlist-1.3.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:086ca1ac0a40e722d6833d4ce74f5bf1aba2c77cbfdc0cd83722ffea6da52a04"}, + {file = "frozenlist-1.3.1-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1b51eb355e7f813bcda00276b0114c4172872dc5fb30e3fea059b9367c18fbcb"}, + {file = "frozenlist-1.3.1-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:74140933d45271c1a1283f708c35187f94e1256079b3c43f0c2267f9db5845ff"}, + {file = "frozenlist-1.3.1-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ee4c5120ddf7d4dd1eaf079af3af7102b56d919fa13ad55600a4e0ebe532779b"}, + {file = "frozenlist-1.3.1-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:97d9e00f3ac7c18e685320601f91468ec06c58acc185d18bb8e511f196c8d4b2"}, + {file = "frozenlist-1.3.1-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:6e19add867cebfb249b4e7beac382d33215d6d54476bb6be46b01f8cafb4878b"}, + {file = "frozenlist-1.3.1-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:a027f8f723d07c3f21963caa7d585dcc9b089335565dabe9c814b5f70c52705a"}, + {file = "frozenlist-1.3.1-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:61d7857950a3139bce035ad0b0945f839532987dfb4c06cfe160254f4d19df03"}, + {file = "frozenlist-1.3.1-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:53b2b45052e7149ee8b96067793db8ecc1ae1111f2f96fe1f88ea5ad5fd92d10"}, + {file = "frozenlist-1.3.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:bbb1a71b1784e68870800b1bc9f3313918edc63dbb8f29fbd2e767ce5821696c"}, + {file = "frozenlist-1.3.1-cp37-cp37m-win32.whl", hash = "sha256:ab6fa8c7871877810e1b4e9392c187a60611fbf0226a9e0b11b7b92f5ac72792"}, + {file = "frozenlist-1.3.1-cp37-cp37m-win_amd64.whl", hash = "sha256:f89139662cc4e65a4813f4babb9ca9544e42bddb823d2ec434e18dad582543bc"}, + {file = "frozenlist-1.3.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:4c0c99e31491a1d92cde8648f2e7ccad0e9abb181f6ac3ddb9fc48b63301808e"}, + {file = "frozenlist-1.3.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:61e8cb51fba9f1f33887e22488bad1e28dd8325b72425f04517a4d285a04c519"}, + {file = "frozenlist-1.3.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:cc2f3e368ee5242a2cbe28323a866656006382872c40869b49b265add546703f"}, + {file = "frozenlist-1.3.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:58fb94a01414cddcdc6839807db77ae8057d02ddafc94a42faee6004e46c9ba8"}, + {file = "frozenlist-1.3.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:022178b277cb9277d7d3b3f2762d294f15e85cd2534047e68a118c2bb0058f3e"}, + {file = "frozenlist-1.3.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:572ce381e9fe027ad5e055f143763637dcbac2542cfe27f1d688846baeef5170"}, + {file = "frozenlist-1.3.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:19127f8dcbc157ccb14c30e6f00392f372ddb64a6ffa7106b26ff2196477ee9f"}, + {file = "frozenlist-1.3.1-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:42719a8bd3792744c9b523674b752091a7962d0d2d117f0b417a3eba97d1164b"}, + {file = "frozenlist-1.3.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:2743bb63095ef306041c8f8ea22bd6e4d91adabf41887b1ad7886c4c1eb43d5f"}, + {file = "frozenlist-1.3.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:fa47319a10e0a076709644a0efbcaab9e91902c8bd8ef74c6adb19d320f69b83"}, + {file = "frozenlist-1.3.1-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:52137f0aea43e1993264a5180c467a08a3e372ca9d378244c2d86133f948b26b"}, + {file = "frozenlist-1.3.1-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:f5abc8b4d0c5b556ed8cd41490b606fe99293175a82b98e652c3f2711b452988"}, + {file = "frozenlist-1.3.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:1e1cf7bc8cbbe6ce3881863671bac258b7d6bfc3706c600008925fb799a256e2"}, + {file = "frozenlist-1.3.1-cp38-cp38-win32.whl", hash = "sha256:0dde791b9b97f189874d654c55c24bf7b6782343e14909c84beebd28b7217845"}, + {file = "frozenlist-1.3.1-cp38-cp38-win_amd64.whl", hash = "sha256:9494122bf39da6422b0972c4579e248867b6b1b50c9b05df7e04a3f30b9a413d"}, + {file = "frozenlist-1.3.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:31bf9539284f39ff9398deabf5561c2b0da5bb475590b4e13dd8b268d7a3c5c1"}, + {file = "frozenlist-1.3.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:e0c8c803f2f8db7217898d11657cb6042b9b0553a997c4a0601f48a691480fab"}, + {file = "frozenlist-1.3.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:da5ba7b59d954f1f214d352308d1d86994d713b13edd4b24a556bcc43d2ddbc3"}, + {file = "frozenlist-1.3.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:74e6b2b456f21fc93ce1aff2b9728049f1464428ee2c9752a4b4f61e98c4db96"}, + {file = "frozenlist-1.3.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:526d5f20e954d103b1d47232e3839f3453c02077b74203e43407b962ab131e7b"}, + {file = "frozenlist-1.3.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b499c6abe62a7a8d023e2c4b2834fce78a6115856ae95522f2f974139814538c"}, + {file = "frozenlist-1.3.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ab386503f53bbbc64d1ad4b6865bf001414930841a870fc97f1546d4d133f141"}, + {file = "frozenlist-1.3.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5f63c308f82a7954bf8263a6e6de0adc67c48a8b484fab18ff87f349af356efd"}, + {file = "frozenlist-1.3.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:12607804084d2244a7bd4685c9d0dca5df17a6a926d4f1967aa7978b1028f89f"}, + {file = "frozenlist-1.3.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:da1cdfa96425cbe51f8afa43e392366ed0b36ce398f08b60de6b97e3ed4affef"}, + {file = "frozenlist-1.3.1-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:f810e764617b0748b49a731ffaa525d9bb36ff38332411704c2400125af859a6"}, + {file = "frozenlist-1.3.1-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:35c3d79b81908579beb1fb4e7fcd802b7b4921f1b66055af2578ff7734711cfa"}, + {file = "frozenlist-1.3.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:c92deb5d9acce226a501b77307b3b60b264ca21862bd7d3e0c1f3594022f01bc"}, + {file = "frozenlist-1.3.1-cp39-cp39-win32.whl", hash = "sha256:5e77a8bd41e54b05e4fb2708dc6ce28ee70325f8c6f50f3df86a44ecb1d7a19b"}, + {file = "frozenlist-1.3.1-cp39-cp39-win_amd64.whl", hash = "sha256:625d8472c67f2d96f9a4302a947f92a7adbc1e20bedb6aff8dbc8ff039ca6189"}, + {file = "frozenlist-1.3.1.tar.gz", hash = "sha256:3a735e4211a04ccfa3f4833547acdf5d2f863bfeb01cfd3edaffbc251f15cec8"}, +] future = [ {file = "future-0.18.2.tar.gz", hash = "sha256:b1bead90b70cf6ec3f0710ae53a525360fa360d306a86583adc6bf83a4db537d"}, ] @@ -1556,8 +1737,8 @@ htmlmin = [ {file = "htmlmin-0.1.12.tar.gz", hash = "sha256:50c1ef4630374a5d723900096a961cff426dff46b48f34d194a81bbe14eca178"}, ] hypercorn = [ - {file = "Hypercorn-0.13.2-py3-none-any.whl", hash = "sha256:ca18f91ab3fa823cbe9e949738f9f2cc07027cd647c80d8f93e4b1a2a175f112"}, - {file = "Hypercorn-0.13.2.tar.gz", hash = "sha256:6307be5cbdf6ba411967d4661202dc4f79bd511b5d318bc4eed88b09418427f8"}, + {file = "Hypercorn-0.14.3-py3-none-any.whl", hash = "sha256:7c491d5184f28ee960dcdc14ab45d14633ca79d72ddd13cf4fcb4cb854d679ab"}, + {file = "Hypercorn-0.14.3.tar.gz", hash = "sha256:4a87a0b7bbe9dc75fab06dbe4b301b9b90416e9866c23a377df21a969d6ab8dd"}, ] hyperframe = [ {file = "hyperframe-6.0.1-py3-none-any.whl", hash = "sha256:0ec6bafd80d8ad2195c4f03aacba3a8265e57bc4cff261e802bf39970ed02a15"}, @@ -1568,7 +1749,12 @@ idna = [ {file = "idna-3.3.tar.gz", hash = "sha256:9d643ff0a55b762d5cdb124b8eaa99c66322e2157b69160bc32796e824360e6d"}, ] imagehash = [ - {file = "ImageHash-4.2.1.tar.gz", hash = "sha256:a4af957814bc9832d9241247ff03f76e778f890c18147900b4540af124e93011"}, + {file = "ImageHash-4.3.0-py2.py3-none-any.whl", hash = "sha256:afe0bcb17c88e6f28fd69b91f05c4f207b3d5018431c3f3e632af2162e451d03"}, + {file = "ImageHash-4.3.0.tar.gz", hash = "sha256:3e843fed202105374b71767f93e6791a476dc92d212668dd3a6db61bfb18faf9"}, +] +importlib-metadata = [ + {file = "importlib_metadata-4.12.0-py3-none-any.whl", hash = "sha256:7401a975809ea1fdc658c3aa4f78cc2195a0e019c5cbc4c06122884e9ae80c23"}, + {file = "importlib_metadata-4.12.0.tar.gz", hash = "sha256:637245b8bab2b6502fcbc752cc4b7a6f6243bb02b31c5c26156ad103d3d45670"}, ] iniconfig = [ {file = "iniconfig-1.1.1-py2.py3-none-any.whl", hash = "sha256:011e24c64b7f47f6ebd835bb12a743f2fbe9a26d4cecaa7f53bc4f35ee9da8b3"}, @@ -1583,8 +1769,8 @@ itsdangerous = [ {file = "itsdangerous-2.1.2.tar.gz", hash = "sha256:5dbbc68b317e5e42f327f9021763545dc3fc3bfe22e6deb96aaf1fc38874156a"}, ] jinja2 = [ - {file = "Jinja2-3.0.3-py3-none-any.whl", hash = "sha256:077ce6014f7b40d03b47d1f1ca4b0fc8328a692bd284016f806ed0eaca390ad8"}, - {file = "Jinja2-3.0.3.tar.gz", hash = "sha256:611bb273cd68f3b993fabdc4064fc858c5b47a973cb5aa7999ec1ba405c87cd7"}, + {file = "Jinja2-3.1.2-py3-none-any.whl", hash = "sha256:6088930bfe239f0e6710546ab9c19c9ef35e29792895fed6e6e31a023a182a61"}, + {file = "Jinja2-3.1.2.tar.gz", hash = "sha256:31351a702a408a9e7595a8fc6150fc3f43bb6bf7e319770cbc0db9df9437e852"}, ] jmespath = [ {file = "jmespath-1.0.1-py3-none-any.whl", hash = "sha256:02e2e4cc71b5bcab88332eebf907519190dd9e6e82107fa7f83b1003a6252980"}, @@ -1870,27 +2056,27 @@ packaging = [ {file = "packaging-21.3.tar.gz", hash = "sha256:dd47c42927d89ab911e606518907cc2d3a1f38bbd026385970643f9c5b8ecfeb"}, ] pandas = [ - {file = "pandas-1.4.3-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:d51674ed8e2551ef7773820ef5dab9322be0828629f2cbf8d1fc31a0c4fed640"}, - {file = "pandas-1.4.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:16ad23db55efcc93fa878f7837267973b61ea85d244fc5ff0ccbcfa5638706c5"}, - {file = "pandas-1.4.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:958a0588149190c22cdebbc0797e01972950c927a11a900fe6c2296f207b1d6f"}, - {file = "pandas-1.4.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e48fbb64165cda451c06a0f9e4c7a16b534fcabd32546d531b3c240ce2844112"}, - {file = "pandas-1.4.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6f803320c9da732cc79210d7e8cc5c8019aad512589c910c66529eb1b1818230"}, - {file = "pandas-1.4.3-cp310-cp310-win_amd64.whl", hash = "sha256:2893e923472a5e090c2d5e8db83e8f907364ec048572084c7d10ef93546be6d1"}, - {file = "pandas-1.4.3-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:24ea75f47bbd5574675dae21d51779a4948715416413b30614c1e8b480909f81"}, - {file = "pandas-1.4.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:d5ebc990bd34f4ac3c73a2724c2dcc9ee7bf1ce6cf08e87bb25c6ad33507e318"}, - {file = "pandas-1.4.3-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:d6c0106415ff1a10c326c49bc5dd9ea8b9897a6ca0c8688eb9c30ddec49535ef"}, - {file = "pandas-1.4.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:78b00429161ccb0da252229bcda8010b445c4bf924e721265bec5a6e96a92e92"}, - {file = "pandas-1.4.3-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6dfbf16b1ea4f4d0ee11084d9c026340514d1d30270eaa82a9f1297b6c8ecbf0"}, - {file = "pandas-1.4.3-cp38-cp38-win32.whl", hash = "sha256:48350592665ea3cbcd07efc8c12ff12d89be09cd47231c7925e3b8afada9d50d"}, - {file = "pandas-1.4.3-cp38-cp38-win_amd64.whl", hash = "sha256:605d572126eb4ab2eadf5c59d5d69f0608df2bf7bcad5c5880a47a20a0699e3e"}, - {file = "pandas-1.4.3-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:a3924692160e3d847e18702bb048dc38e0e13411d2b503fecb1adf0fcf950ba4"}, - {file = "pandas-1.4.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:07238a58d7cbc8a004855ade7b75bbd22c0db4b0ffccc721556bab8a095515f6"}, - {file = "pandas-1.4.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:755679c49460bd0d2f837ab99f0a26948e68fa0718b7e42afbabd074d945bf84"}, - {file = "pandas-1.4.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:41fc406e374590a3d492325b889a2686b31e7a7780bec83db2512988550dadbf"}, - {file = "pandas-1.4.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1d9382f72a4f0e93909feece6fef5500e838ce1c355a581b3d8f259839f2ea76"}, - {file = "pandas-1.4.3-cp39-cp39-win32.whl", hash = "sha256:0daf876dba6c622154b2e6741f29e87161f844e64f84801554f879d27ba63c0d"}, - {file = "pandas-1.4.3-cp39-cp39-win_amd64.whl", hash = "sha256:721a3dd2f06ef942f83a819c0f3f6a648b2830b191a72bbe9451bcd49c3bd42e"}, - {file = "pandas-1.4.3.tar.gz", hash = "sha256:2ff7788468e75917574f080cd4681b27e1a7bf36461fe968b49a87b5a54d007c"}, + {file = "pandas-1.4.4-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:799e6a25932df7e6b1f8dabf63de064e2205dc309abb75956126a0453fd88e97"}, + {file = "pandas-1.4.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:7cd1d69a387f7d5e1a5a06a87574d9ef2433847c0e78113ab51c84d3a8bcaeaa"}, + {file = "pandas-1.4.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:94f2ed1fd51e545ebf71da1e942fe1822ee01e10d3dd2a7276d01351333b7c6b"}, + {file = "pandas-1.4.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4591cadd06fbbbd16fafc2de6e840c1aaefeae3d5864b688004777ef1bbdede3"}, + {file = "pandas-1.4.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d0022fe6a313df1c4869b5edc012d734c6519a6fffa3cf70930f32e6a1078e49"}, + {file = "pandas-1.4.4-cp310-cp310-win_amd64.whl", hash = "sha256:785e878a6e6d8ddcdb8c181e600855402750052497d7fc6d6b508894f6b8830b"}, + {file = "pandas-1.4.4-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:c4bb8b0ab9f94207d07e401d24baebfc63057246b1a5e0cd9ee50df85a656871"}, + {file = "pandas-1.4.4-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:51c424ca134fdaeac9a4acd719d1ab48046afc60943a489028f0413fdbe9ef1c"}, + {file = "pandas-1.4.4-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:ce35f947202b0b99c660221d82beb91d2e6d553d55a40b30128204e3e2c63848"}, + {file = "pandas-1.4.4-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ee6f1848148ed3204235967613b0a32be2d77f214e9623f554511047705c1e04"}, + {file = "pandas-1.4.4-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e7cc960959be28d064faefc0cb2aef854d46b827c004ebea7e79b5497ed83e7d"}, + {file = "pandas-1.4.4-cp38-cp38-win32.whl", hash = "sha256:9d805bce209714b1c1fa29bfb1e42ad87e4c0a825e4b390c56a3e71593b7e8d8"}, + {file = "pandas-1.4.4-cp38-cp38-win_amd64.whl", hash = "sha256:afbddad78a98ec4d2ce08b384b81730de1ccc975b99eb663e6dac43703f36d98"}, + {file = "pandas-1.4.4-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:a08ceb59db499864c58a9bf85ab6219d527d91f14c0240cc25fa2c261032b2a7"}, + {file = "pandas-1.4.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:0959c41004e3d2d16f39c828d6da66ebee329836a7ecee49fb777ac9ad8a7501"}, + {file = "pandas-1.4.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:87b4194f344dcd14c0f885cecb22005329b38bda10f1aaf7b9596a00ec8a4768"}, + {file = "pandas-1.4.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9d2a7a3c1fea668d56bd91edbd5f2732e0af8feb9d2bf8d9bfacb2dea5fa9536"}, + {file = "pandas-1.4.4-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a981cfabf51c318a562deb4ae7deec594c07aee7cf18b4594a92c23718ec8275"}, + {file = "pandas-1.4.4-cp39-cp39-win32.whl", hash = "sha256:050aada67a5ec6699a7879e769825b510018a95fb9ac462bb1867483d0974a97"}, + {file = "pandas-1.4.4-cp39-cp39-win_amd64.whl", hash = "sha256:8d4d2fe2863ecddb0ba1979bdda26c8bc2ea138f5a979abe3ba80c0fa4015c91"}, + {file = "pandas-1.4.4.tar.gz", hash = "sha256:ab6c0d738617b675183e5f28db32b5148b694ad9bba0a40c3ea26d96b431db67"}, ] pandas-profiling = [ {file = "pandas-profiling-3.2.0.tar.gz", hash = "sha256:8897c2628e3a9b5c915d038aa1782be7fa9e322e405a51aa4586466ab84e8461"}, @@ -1995,41 +2181,42 @@ pycodestyle = [ {file = "pycodestyle-2.9.1.tar.gz", hash = "sha256:2c9607871d58c76354b697b42f5d57e1ada7d261c261efac224b664affdc5785"}, ] pydantic = [ - {file = "pydantic-1.9.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:9c9e04a6cdb7a363d7cb3ccf0efea51e0abb48e180c0d31dca8d247967d85c6e"}, - {file = "pydantic-1.9.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:fafe841be1103f340a24977f61dee76172e4ae5f647ab9e7fd1e1fca51524f08"}, - {file = "pydantic-1.9.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:afacf6d2a41ed91fc631bade88b1d319c51ab5418870802cedb590b709c5ae3c"}, - {file = "pydantic-1.9.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3ee0d69b2a5b341fc7927e92cae7ddcfd95e624dfc4870b32a85568bd65e6131"}, - {file = "pydantic-1.9.2-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:ff68fc85355532ea77559ede81f35fff79a6a5543477e168ab3a381887caea76"}, - {file = "pydantic-1.9.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:c0f5e142ef8217019e3eef6ae1b6b55f09a7a15972958d44fbd228214cede567"}, - {file = "pydantic-1.9.2-cp310-cp310-win_amd64.whl", hash = "sha256:615661bfc37e82ac677543704437ff737418e4ea04bef9cf11c6d27346606044"}, - {file = "pydantic-1.9.2-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:328558c9f2eed77bd8fffad3cef39dbbe3edc7044517f4625a769d45d4cf7555"}, - {file = "pydantic-1.9.2-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2bd446bdb7755c3a94e56d7bdfd3ee92396070efa8ef3a34fab9579fe6aa1d84"}, - {file = "pydantic-1.9.2-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e0b214e57623a535936005797567231a12d0da0c29711eb3514bc2b3cd008d0f"}, - {file = "pydantic-1.9.2-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:d8ce3fb0841763a89322ea0432f1f59a2d3feae07a63ea2c958b2315e1ae8adb"}, - {file = "pydantic-1.9.2-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:b34ba24f3e2d0b39b43f0ca62008f7ba962cff51efa56e64ee25c4af6eed987b"}, - {file = "pydantic-1.9.2-cp36-cp36m-win_amd64.whl", hash = "sha256:84d76ecc908d917f4684b354a39fd885d69dd0491be175f3465fe4b59811c001"}, - {file = "pydantic-1.9.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:4de71c718c9756d679420c69f216776c2e977459f77e8f679a4a961dc7304a56"}, - {file = "pydantic-1.9.2-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5803ad846cdd1ed0d97eb00292b870c29c1f03732a010e66908ff48a762f20e4"}, - {file = "pydantic-1.9.2-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a8c5360a0297a713b4123608a7909e6869e1b56d0e96eb0d792c27585d40757f"}, - {file = "pydantic-1.9.2-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:cdb4272678db803ddf94caa4f94f8672e9a46bae4a44f167095e4d06fec12979"}, - {file = "pydantic-1.9.2-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:19b5686387ea0d1ea52ecc4cffb71abb21702c5e5b2ac626fd4dbaa0834aa49d"}, - {file = "pydantic-1.9.2-cp37-cp37m-win_amd64.whl", hash = "sha256:32e0b4fb13ad4db4058a7c3c80e2569adbd810c25e6ca3bbd8b2a9cc2cc871d7"}, - {file = "pydantic-1.9.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:91089b2e281713f3893cd01d8e576771cd5bfdfbff5d0ed95969f47ef6d676c3"}, - {file = "pydantic-1.9.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:e631c70c9280e3129f071635b81207cad85e6c08e253539467e4ead0e5b219aa"}, - {file = "pydantic-1.9.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4b3946f87e5cef3ba2e7bd3a4eb5a20385fe36521d6cc1ebf3c08a6697c6cfb3"}, - {file = "pydantic-1.9.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5565a49effe38d51882cb7bac18bda013cdb34d80ac336428e8908f0b72499b0"}, - {file = "pydantic-1.9.2-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:bd67cb2c2d9602ad159389c29e4ca964b86fa2f35c2faef54c3eb28b4efd36c8"}, - {file = "pydantic-1.9.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:4aafd4e55e8ad5bd1b19572ea2df546ccace7945853832bb99422a79c70ce9b8"}, - {file = "pydantic-1.9.2-cp38-cp38-win_amd64.whl", hash = "sha256:d70916235d478404a3fa8c997b003b5f33aeac4686ac1baa767234a0f8ac2326"}, - {file = "pydantic-1.9.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:f0ca86b525264daa5f6b192f216a0d1e860b7383e3da1c65a1908f9c02f42801"}, - {file = "pydantic-1.9.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:1061c6ee6204f4f5a27133126854948e3b3d51fcc16ead2e5d04378c199b2f44"}, - {file = "pydantic-1.9.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e78578f0c7481c850d1c969aca9a65405887003484d24f6110458fb02cca7747"}, - {file = "pydantic-1.9.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5da164119602212a3fe7e3bc08911a89db4710ae51444b4224c2382fd09ad453"}, - {file = "pydantic-1.9.2-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:7ead3cd020d526f75b4188e0a8d71c0dbbe1b4b6b5dc0ea775a93aca16256aeb"}, - {file = "pydantic-1.9.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:7d0f183b305629765910eaad707800d2f47c6ac5bcfb8c6397abdc30b69eeb15"}, - {file = "pydantic-1.9.2-cp39-cp39-win_amd64.whl", hash = "sha256:f1a68f4f65a9ee64b6ccccb5bf7e17db07caebd2730109cb8a95863cfa9c4e55"}, - {file = "pydantic-1.9.2-py3-none-any.whl", hash = "sha256:78a4d6bdfd116a559aeec9a4cfe77dda62acc6233f8b56a716edad2651023e5e"}, - {file = "pydantic-1.9.2.tar.gz", hash = "sha256:8cb0bc509bfb71305d7a59d00163d5f9fc4530f0881ea32c74ff4f74c85f3d3d"}, + {file = "pydantic-1.10.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:bb6ad4489af1bac6955d38ebcb95079a836af31e4c4f74aba1ca05bb9f6027bd"}, + {file = "pydantic-1.10.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:a1f5a63a6dfe19d719b1b6e6106561869d2efaca6167f84f5ab9347887d78b98"}, + {file = "pydantic-1.10.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:352aedb1d71b8b0736c6d56ad2bd34c6982720644b0624462059ab29bd6e5912"}, + {file = "pydantic-1.10.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:19b3b9ccf97af2b7519c42032441a891a5e05c68368f40865a90eb88833c2559"}, + {file = "pydantic-1.10.2-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:e9069e1b01525a96e6ff49e25876d90d5a563bc31c658289a8772ae186552236"}, + {file = "pydantic-1.10.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:355639d9afc76bcb9b0c3000ddcd08472ae75318a6eb67a15866b87e2efa168c"}, + {file = "pydantic-1.10.2-cp310-cp310-win_amd64.whl", hash = "sha256:ae544c47bec47a86bc7d350f965d8b15540e27e5aa4f55170ac6a75e5f73b644"}, + {file = "pydantic-1.10.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:a4c805731c33a8db4b6ace45ce440c4ef5336e712508b4d9e1aafa617dc9907f"}, + {file = "pydantic-1.10.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d49f3db871575e0426b12e2f32fdb25e579dea16486a26e5a0474af87cb1ab0a"}, + {file = "pydantic-1.10.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:37c90345ec7dd2f1bcef82ce49b6235b40f282b94d3eec47e801baf864d15525"}, + {file = "pydantic-1.10.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7b5ba54d026c2bd2cb769d3468885f23f43710f651688e91f5fb1edcf0ee9283"}, + {file = "pydantic-1.10.2-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:05e00dbebbe810b33c7a7362f231893183bcc4251f3f2ff991c31d5c08240c42"}, + {file = "pydantic-1.10.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:2d0567e60eb01bccda3a4df01df677adf6b437958d35c12a3ac3e0f078b0ee52"}, + {file = "pydantic-1.10.2-cp311-cp311-win_amd64.whl", hash = "sha256:c6f981882aea41e021f72779ce2a4e87267458cc4d39ea990729e21ef18f0f8c"}, + {file = "pydantic-1.10.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:c4aac8e7103bf598373208f6299fa9a5cfd1fc571f2d40bf1dd1955a63d6eeb5"}, + {file = "pydantic-1.10.2-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:81a7b66c3f499108b448f3f004801fcd7d7165fb4200acb03f1c2402da73ce4c"}, + {file = "pydantic-1.10.2-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bedf309630209e78582ffacda64a21f96f3ed2e51fbf3962d4d488e503420254"}, + {file = "pydantic-1.10.2-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:9300fcbebf85f6339a02c6994b2eb3ff1b9c8c14f502058b5bf349d42447dcf5"}, + {file = "pydantic-1.10.2-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:216f3bcbf19c726b1cc22b099dd409aa371f55c08800bcea4c44c8f74b73478d"}, + {file = "pydantic-1.10.2-cp37-cp37m-win_amd64.whl", hash = "sha256:dd3f9a40c16daf323cf913593083698caee97df2804aa36c4b3175d5ac1b92a2"}, + {file = "pydantic-1.10.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:b97890e56a694486f772d36efd2ba31612739bc6f3caeee50e9e7e3ebd2fdd13"}, + {file = "pydantic-1.10.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:9cabf4a7f05a776e7793e72793cd92cc865ea0e83a819f9ae4ecccb1b8aa6116"}, + {file = "pydantic-1.10.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:06094d18dd5e6f2bbf93efa54991c3240964bb663b87729ac340eb5014310624"}, + {file = "pydantic-1.10.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cc78cc83110d2f275ec1970e7a831f4e371ee92405332ebfe9860a715f8336e1"}, + {file = "pydantic-1.10.2-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:1ee433e274268a4b0c8fde7ad9d58ecba12b069a033ecc4645bb6303c062d2e9"}, + {file = "pydantic-1.10.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:7c2abc4393dea97a4ccbb4ec7d8658d4e22c4765b7b9b9445588f16c71ad9965"}, + {file = "pydantic-1.10.2-cp38-cp38-win_amd64.whl", hash = "sha256:0b959f4d8211fc964772b595ebb25f7652da3f22322c007b6fed26846a40685e"}, + {file = "pydantic-1.10.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:c33602f93bfb67779f9c507e4d69451664524389546bacfe1bee13cae6dc7488"}, + {file = "pydantic-1.10.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:5760e164b807a48a8f25f8aa1a6d857e6ce62e7ec83ea5d5c5a802eac81bad41"}, + {file = "pydantic-1.10.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6eb843dcc411b6a2237a694f5e1d649fc66c6064d02b204a7e9d194dff81eb4b"}, + {file = "pydantic-1.10.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4b8795290deaae348c4eba0cebb196e1c6b98bdbe7f50b2d0d9a4a99716342fe"}, + {file = "pydantic-1.10.2-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:e0bedafe4bc165ad0a56ac0bd7695df25c50f76961da29c050712596cf092d6d"}, + {file = "pydantic-1.10.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:2e05aed07fa02231dbf03d0adb1be1d79cabb09025dd45aa094aa8b4e7b9dcda"}, + {file = "pydantic-1.10.2-cp39-cp39-win_amd64.whl", hash = "sha256:c1ba1afb396148bbc70e9eaa8c06c1716fdddabaf86e7027c5988bae2a829ab6"}, + {file = "pydantic-1.10.2-py3-none-any.whl", hash = "sha256:1b6ee725bd6e83ec78b1aa32c5b1fa67a3a65badddde3976bca5fe4568f27709"}, + {file = "pydantic-1.10.2.tar.gz", hash = "sha256:91b8e218852ef6007c2b98cd861601c6a09f1aa32bbbb74fab5b1c33d4a1e410"}, ] pyflakes = [ {file = "pyflakes-2.5.0-py2.py3-none-any.whl", hash = "sha256:4579f67d887f804e67edb544428f264b7b24f435b263c4614f384135cea553d2"}, @@ -2040,16 +2227,16 @@ pyparsing = [ {file = "pyparsing-3.0.9.tar.gz", hash = "sha256:2b020ecf7d21b687f219b71ecad3631f644a47f01403fa1d1036b0c6416d70fb"}, ] pytest = [ - {file = "pytest-7.1.2-py3-none-any.whl", hash = "sha256:13d0e3ccfc2b6e26be000cb6568c832ba67ba32e719443bfe725814d3c42433c"}, - {file = "pytest-7.1.2.tar.gz", hash = "sha256:a06a0425453864a270bc45e71f783330a7428defb4230fb5e6a731fde06ecd45"}, + {file = "pytest-7.1.3-py3-none-any.whl", hash = "sha256:1377bda3466d70b55e3f5cecfa55bb7cfcf219c7964629b967c37cf0bda818b7"}, + {file = "pytest-7.1.3.tar.gz", hash = "sha256:4f365fec2dff9c1162f834d9f18af1ba13062db0c708bf7b946f8a5c76180c39"}, ] pytest-asyncio = [ {file = "pytest-asyncio-0.19.0.tar.gz", hash = "sha256:ac4ebf3b6207259750bc32f4c1d8fcd7e79739edbc67ad0c58dd150b1d072fed"}, {file = "pytest_asyncio-0.19.0-py3-none-any.whl", hash = "sha256:7a97e37cfe1ed296e2e84941384bdd37c376453912d397ed39293e0916f521fa"}, ] pytest-cov = [ - {file = "pytest-cov-2.5.1.tar.gz", hash = "sha256:03aa752cf11db41d281ea1d807d954c4eda35cfa1b21d6971966cc041bbf6e2d"}, - {file = "pytest_cov-2.5.1-py2.py3-none-any.whl", hash = "sha256:890fe5565400902b0c78b5357004aab1c814115894f4f21370e2433256a3eeec"}, + {file = "pytest-cov-3.0.0.tar.gz", hash = "sha256:e7f0f5b1617d2210a2cabc266dfe2f4c75a8d32fb89eafb7ad9d06f6d076d470"}, + {file = "pytest_cov-3.0.0-py3-none-any.whl", hash = "sha256:578d5d15ac4a25e5f961c938b85a05b09fdaae9deef3bb6de9a6e766622ca7a6"}, ] python-dateutil = [ {file = "python-dateutil-2.8.2.tar.gz", hash = "sha256:0123cacc1627ae19ddf3c27a5de5bd67ee4586fbdd6440d9748f8abb483d3e86"}, @@ -2060,8 +2247,8 @@ python-slugify = [ {file = "python_slugify-6.1.2-py2.py3-none-any.whl", hash = "sha256:7b2c274c308b62f4269a9ba701aa69a797e9bca41aeee5b3a9e79e36b6656927"}, ] python-stdnum = [ - {file = "python-stdnum-1.15.tar.gz", hash = "sha256:ff0e4d2b8731711cf2a73d22964139208088b4bd1a3e3b0c4f78d6e823dd8923"}, - {file = "python_stdnum-1.15-py2.py3-none-any.whl", hash = "sha256:46c05dff2d5ff89b12cc63ab54cca1e4788e4e3087c163a1b7434a53fa926f28"}, + {file = "python-stdnum-1.17.tar.gz", hash = "sha256:374e2b5e13912ccdbf50b0b23fca2c3e0531174805c32d74e145f37756328340"}, + {file = "python_stdnum-1.17-py2.py3-none-any.whl", hash = "sha256:a46e6cf9652807314d369b654b255c86a59f93d18be2834f3d567ed1a346c547"}, ] pytimeparse = [ {file = "pytimeparse-1.1.8-py2.py3-none-any.whl", hash = "sha256:04b7be6cc8bd9f5647a6325444926c3ac34ee6bc7e69da4367ba282f076036bd"}, @@ -2139,20 +2326,20 @@ pyyaml = [ {file = "PyYAML-6.0.tar.gz", hash = "sha256:68fb519c14306fec9720a2a5b45bc9f0c8d1b9c72adf45c37baedfcd949c35a2"}, ] quart = [ - {file = "Quart-0.14.1-py3-none-any.whl", hash = "sha256:7b13786e07541cc9ce1466fdc6a6ccd5f36eb39118edd25a42d617593cd17707"}, - {file = "Quart-0.14.1.tar.gz", hash = "sha256:429c5b4ff27e1d2f9ca0aacc38f6aba0ff49b38b815448bf24b613d3de12ea02"}, + {file = "Quart-0.18.0-py3-none-any.whl", hash = "sha256:5bef6711d25e379ce8a1ed3f7d3ec72eae4159d116e8d480d1cab59c8bb6f44d"}, + {file = "Quart-0.18.0.tar.gz", hash = "sha256:0c2465ef5768890431650636f51fe2cc459de6cf4cf3363870ae2a1f3dba9eb1"}, ] quart-cors = [ - {file = "Quart-CORS-0.3.0.tar.gz", hash = "sha256:c08bdb326219b6c186d19ed6a97a7fd02de8fe36c7856af889494c69b525c53c"}, - {file = "Quart_CORS-0.3.0-py3-none-any.whl", hash = "sha256:020a17d504264db86cada3c1335ef174af28b33f57cee321ddc46d69c33d5c8e"}, + {file = "Quart-CORS-0.5.0.tar.gz", hash = "sha256:ea08d26aef918d59194fbf065cde9b6cae90dc5f21120dcd254d7d46190cd293"}, + {file = "Quart_CORS-0.5.0-py3-none-any.whl", hash = "sha256:c2be932f20413a56b176527090229afe8f725a3ee029d45ea08a174cdc319823"}, ] requests = [ - {file = "requests-2.27.1-py2.py3-none-any.whl", hash = "sha256:f22fa1e554c9ddfd16e6e41ac79759e17be9e492b3587efa038054674760e72d"}, - {file = "requests-2.27.1.tar.gz", hash = "sha256:68d7c56fd5a8999887728ef304a6d12edc7be74f1cfa47714fc8b414525c9a61"}, + {file = "requests-2.28.1-py3-none-any.whl", hash = "sha256:8fefa2a1a1365bf5520aac41836fbee479da67864514bdb821f31ce07ce65349"}, + {file = "requests-2.28.1.tar.gz", hash = "sha256:7c5599b102feddaa661c826c56ab4fee28bfd17f5abca1ebbe3e7f19d7c97983"}, ] s3transfer = [ - {file = "s3transfer-0.5.2-py3-none-any.whl", hash = "sha256:7a6f4c4d1fdb9a2b640244008e142cbc2cd3ae34b386584ef044dd0f27101971"}, - {file = "s3transfer-0.5.2.tar.gz", hash = "sha256:95c58c194ce657a5f4fb0b9e60a84968c808888aed628cd98ab8771fe1db98ed"}, + {file = "s3transfer-0.6.0-py3-none-any.whl", hash = "sha256:06176b74f3a15f61f1b4f25a1fc29a4429040b7647133a463da8fa5bd28d5ecd"}, + {file = "s3transfer-0.6.0.tar.gz", hash = "sha256:2ed07d3866f523cc561bf4a00fc5535827981b117dd7876f036b0c1aca42c947"}, ] scipy = [ {file = "scipy-1.6.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:a15a1f3fc0abff33e792d6049161b7795909b40b97c6cc2934ed54384017ab76"}, @@ -2176,12 +2363,12 @@ scipy = [ {file = "scipy-1.6.1.tar.gz", hash = "sha256:c4fceb864890b6168e79b0e714c585dbe2fd4222768ee90bc1aa0f8218691b11"}, ] seaborn = [ - {file = "seaborn-0.11.2-py3-none-any.whl", hash = "sha256:85a6baa9b55f81a0623abddc4a26b334653ff4c6b18c418361de19dbba0ef283"}, - {file = "seaborn-0.11.2.tar.gz", hash = "sha256:cf45e9286d40826864be0e3c066f98536982baf701a7caa386511792d61ff4f6"}, + {file = "seaborn-0.12.0-py3-none-any.whl", hash = "sha256:cbeff3deef7c2515aa0af99b2c7e02dc5bf8b42c936a74d8e4b416905b549db0"}, + {file = "seaborn-0.12.0.tar.gz", hash = "sha256:893f17292d8baca616c1578ddb58eb25c72d622f54fc5ee329c8207dc9b57b23"}, ] sentry-sdk = [ - {file = "sentry-sdk-1.0.0.tar.gz", hash = "sha256:71de00c9711926816f750bc0f57ef2abbcb1bfbdf5378c601df7ec978f44857a"}, - {file = "sentry_sdk-1.0.0-py2.py3-none-any.whl", hash = "sha256:9221e985f425913204989d0e0e1cbb719e8b7fa10540f1bc509f660c06a34e66"}, + {file = "sentry-sdk-1.9.8.tar.gz", hash = "sha256:ef4b4d57631662ff81e15c22bf007f7ce07c47321648c8e601fbd22950ed1a79"}, + {file = "sentry_sdk-1.9.8-py2.py3-none-any.whl", hash = "sha256:7378c08d81da78a4860da7b4900ac51fef38be841d01bc5b7cb249ac51e070c6"}, ] setuptools-scm = [ {file = "setuptools_scm-6.4.2-py3-none-any.whl", hash = "sha256:acea13255093849de7ccb11af9e1fb8bde7067783450cee9ef7a93139bddf6d4"}, @@ -2246,8 +2433,8 @@ tomli = [ {file = "tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"}, ] tqdm = [ - {file = "tqdm-4.64.0-py2.py3-none-any.whl", hash = "sha256:74a2cdefe14d11442cedf3ba4e21a3b84ff9a2dbdc6cfae2c34addb2a14a5ea6"}, - {file = "tqdm-4.64.0.tar.gz", hash = "sha256:40be55d30e200777a307a7585aee69e4eabb46b4ec6a4b4a5f2d9f11e7d5408d"}, + {file = "tqdm-4.64.1-py2.py3-none-any.whl", hash = "sha256:6fee160d6ffcd1b1c68c65f14c829c22832bc401726335ce92c52d395944a6a1"}, + {file = "tqdm-4.64.1.tar.gz", hash = "sha256:5f4f682a004951c1b450bc753c710e9280c5746ce6ffedee253ddbcbf54cf1e4"}, ] typing-extensions = [ {file = "typing_extensions-4.3.0-py3-none-any.whl", hash = "sha256:25642c956049920a5aa49edcdd6ab1e06d7e5d467fc00e0506c44ac86fbfca02"}, @@ -2257,21 +2444,24 @@ udata-event-service = [ {file = "udata_event_service-0.0.8-py2.py3-none-any.whl", hash = "sha256:35ea65522fb3ed98c866f2cbe5d9ac5562949c549f5be8657aaecb50088f40d2"}, {file = "udata_event_service-0.0.8.tar.gz", hash = "sha256:27e026cd85b432f65d99010435c2eccedc3d37aecad2128d7e9be650c4b25178"}, ] +unidecode = [ + {file = "Unidecode-1.3.4-py3-none-any.whl", hash = "sha256:afa04efcdd818a93237574791be9b2817d7077c25a068b00f8cff7baa4e59257"}, + {file = "Unidecode-1.3.4.tar.gz", hash = "sha256:8e4352fb93d5a735c788110d2e7ac8e8031eb06ccbfe8d324ab71735015f9342"}, +] urllib3 = [ {file = "urllib3-1.26.12-py2.py3-none-any.whl", hash = "sha256:b930dd878d5a8afb066a637fbb35144fe7901e3b209d1cd4f524bd0e9deee997"}, {file = "urllib3-1.26.12.tar.gz", hash = "sha256:3fa96cf423e6987997fc326ae8df396db2a8b7c667747d47ddd8ecba91f4a74e"}, ] validators = [ - {file = "validators-0.18.2-py3-none-any.whl", hash = "sha256:0143dcca8a386498edaf5780cbd5960da1a4c85e0719f3ee5c9b41249c4fefbd"}, - {file = "validators-0.18.2.tar.gz", hash = "sha256:37cd9a9213278538ad09b5b9f9134266e7c226ab1fede1d500e29e0a8fbb9ea6"}, + {file = "validators-0.20.0.tar.gz", hash = "sha256:24148ce4e64100a2d5e267233e23e7afeb55316b47d30faae7eb6e7292bc226a"}, ] visions = [ {file = "visions-0.7.4-py3-none-any.whl", hash = "sha256:16f007e70362b788478b95129bb044dc0718b7530205ebddd8a1ff34a281d28e"}, {file = "visions-0.7.4.tar.gz", hash = "sha256:4f66c1da369cdbced5e1e2d2c43104caa8cfb9c694006cea1592e1673cfc1153"}, ] werkzeug = [ - {file = "Werkzeug-1.0.1-py2.py3-none-any.whl", hash = "sha256:2de2a5db0baeae7b2d2664949077c2ac63fbd16d98da0ff71837f7d1dea3fd43"}, - {file = "Werkzeug-1.0.1.tar.gz", hash = "sha256:6c80b1e5ad3665290ea39320b91e1be1e0d5f60652b964a3070216de83d2e47c"}, + {file = "Werkzeug-2.2.2-py3-none-any.whl", hash = "sha256:f979ab81f58d7318e064e99c4506445d60135ac5cd2e177a2de0089bfd4c9bd5"}, + {file = "Werkzeug-2.2.2.tar.gz", hash = "sha256:7ea2d48322cc7c0f8b3a215ed73eabd7b5d75d0b50e31ab006286ccff9e00b8f"}, ] wsproto = [ {file = "wsproto-1.2.0-py3-none-any.whl", hash = "sha256:b9acddd652b585d75b20477888c56642fdade28bdfd3579aa24a4d2c037dd736"}, @@ -2342,3 +2532,7 @@ yarl = [ {file = "yarl-1.8.1-cp39-cp39-win_amd64.whl", hash = "sha256:de49d77e968de6626ba7ef4472323f9d2e5a56c1d85b7c0e2a190b2173d3b9be"}, {file = "yarl-1.8.1.tar.gz", hash = "sha256:af887845b8c2e060eb5605ff72b6f2dd2aab7a761379373fd89d314f4752abbf"}, ] +zipp = [ + {file = "zipp-3.8.1-py3-none-any.whl", hash = "sha256:47c40d7fe183a6f21403a199b3e4192cca5774656965b0a4988ad2f8feb5f009"}, + {file = "zipp-3.8.1.tar.gz", hash = "sha256:05b45f1ee8f807d0cc928485ca40a07cb491cf092ff587c0df9cb1fd154848d2"}, +] diff --git a/pyproject.toml b/pyproject.toml index 29ee42a..1fd861d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -9,31 +9,30 @@ license = "MIT" python = ">=3.9,<4" click_default_group = "~1.2.2" click = "~8.1.3" -agate = "~1.6.1" -agate-sql = "~0.5.5" -aiohttp = "~3.7.3" -validators = "~0.18.2" -agate-excel = "~0.2.3" -Quart = "~0.14.1" -quart-cors = "~0.3.0" -sentry-sdk = "~1.0.0" +agate = "~1.6.3" +agate-sql = "~0.5.8" +aiohttp = "~3.8.1" +validators = "~0.20.0" +agate-excel = "~0.2.5" +Quart = "~0.18.0" +quart-cors = "~0.5.0" +sentry-sdk = "~1.9.8" cchardet = "~2.1.7" -python-stdnum = "~1.15" -aiosqlite = "~0.16.1" -pandas = "~1.4.3" +python-stdnum = "~1.17" +aiosqlite = "~0.17.0" +pandas = "~1.4.4" pandas-profiling = "~3.2.0" -requests = "~2.27.1" +requests = "~2.28.1" udata-event-service = "~0.0.8" -boto3 = "~1.21.21" -Jinja2 = "~3.0.3" -Werkzeug = "~1.0.0" +boto3 = "~1.24.66" +csv-detective = "~0.4.5" [tool.poetry.dev-dependencies] -aioresponses = "~0.7.1" -pytest = "~7.1.2" +aioresponses = "~0.7.3" +pytest = "~7.1.3" pytest-asyncio = "~0.19.0" flake8 = "~5.0.4" -pytest-cov = "~2.5.1" +pytest-cov = "~3.0.0" [tool.poetry.scripts] csvapi = "csvapi.cli:cli" diff --git a/tests/test_api.py b/tests/test_api.py index ad52cd0..4b95647 100644 --- a/tests/test_api.py +++ b/tests/test_api.py @@ -269,7 +269,8 @@ async def test_api_objects_shape(client, rmock, uploaded_csv): res = await client.get(f"/api/{MOCK_CSV_HASH}?_shape=objects") assert res.status_code == 200 jsonres = await res.json - assert jsonres['rows'] == [{ + assert jsonres['rows'] == [ + { 'rowid': 1, 'col a': 'data à1', 'col b': 'data b1', @@ -279,14 +280,16 @@ async def test_api_objects_shape(client, rmock, uploaded_csv): 'col a': 'data ª2', 'col b': 'data b2', 'col c': 'a', - }] + } + ] async def test_api_objects_norowid(client, rmock, uploaded_csv): res = await client.get(f"/api/{MOCK_CSV_HASH}?_shape=objects&_rowid=hide") assert res.status_code == 200 jsonres = await res.json - assert jsonres['rows'] == [{ + assert jsonres['rows'] == [ + { 'col a': 'data à1', 'col b': 'data b1', 'col c': 'z', @@ -294,7 +297,8 @@ async def test_api_objects_norowid(client, rmock, uploaded_csv): 'col a': 'data ª2', 'col b': 'data b2', 'col c': 'a', - }] + } + ] async def test_api_objects_nototal(client, rmock, uploaded_csv):