diff --git a/.vscode/settings.json b/.vscode/settings.json index ae6332a..63dc3ab 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -16,7 +16,7 @@ "statusBarItem.hoverBackground": "#5ce637", "statusBar.foreground": "#15202b" }, - "python.pythonPath": ".venv/bin/python", + "python.pythonPath": ".venv/bin/python3", "python.analysis.openFilesOnly": false, "python.analysis.memory.keepLibraryLocalVariables": true, "python.autoComplete.addBrackets": true, diff --git a/api/app/core/middleware.py b/api/app/core/middleware.py index 8805e67..a3fb708 100644 --- a/api/app/core/middleware.py +++ b/api/app/core/middleware.py @@ -3,7 +3,6 @@ import time # Third Party -# Local Imports from loguru import logger from starlette.middleware.base import BaseHTTPMiddleware diff --git a/api/app/main.py b/api/app/main.py index 2dfa8ba..aa46ffb 100755 --- a/api/app/main.py +++ b/api/app/main.py @@ -7,9 +7,9 @@ import time # Third Party -# Third Party Imports from fastapi import FastAPI from fastapi import __version__ as fastapi_version +from fastapi.responses import JSONResponse from loguru import logger from starlette.middleware.cors import CORSMiddleware from starlette.middleware.gzip import GZipMiddleware @@ -19,9 +19,9 @@ from starlette_prometheus import PrometheusMiddleware, metrics # Local -# Local Imports import bel.core.settings as settings from bel.__version__ import __version__ as version +from bel.api.core.middleware import StatsMiddleware from bel.api.endpoints.bel import router as bel_router from bel.api.endpoints.belspec import router as belspec_router from bel.api.endpoints.info import router as info_router @@ -29,7 +29,6 @@ from bel.api.endpoints.orthology import router as orthology_router from bel.api.endpoints.pubmed import router as pubmed_router from bel.api.endpoints.terms import router as terms_router -from core.middleware import StatsMiddleware logger.remove() @@ -58,6 +57,22 @@ version=version, ) +# Added user_flag to HTTPException +@app.exception_handler(HTTPException) +async def http_exception_handler(request: Request, exc: HTTPException) -> JSONResponse: + headers = getattr(exc, "headers", None) + if headers: + return JSONResponse( + {"detail": exc.detail, "user_flag": exc.user_flag}, + status_code=exc.status_code, + headers=headers, + ) + else: + return JSONResponse( + {"detail": exc.detail, "user_flag": exc.user_flag}, status_code=exc.status_code + ) + + logger.info("Starting BEL API") logger.info(f"Fast API Version: {fastapi_version}") diff --git a/bel/api/core/exceptions.py b/bel/api/core/exceptions.py new file mode 100644 index 0000000..ab383fe --- /dev/null +++ b/bel/api/core/exceptions.py @@ -0,0 +1,17 @@ +# Standard Library +from typing import Any, Dict, Optional + +# Third Party +from fastapi.exceptions import HTTPException as FastAPIHTTPException + + +class HTTPException(FastAPIHTTPException): + def __init__( + self, + status_code: int, + detail: Any = None, + user_flag: bool = False, + headers: Optional[Dict[str, Any]] = None, + ) -> None: + super().__init__(status_code=status_code, detail=detail, headers=headers) + self.user_flag = user_flag diff --git a/bel/api/core/jwt.py b/bel/api/core/jwt.py new file mode 100644 index 0000000..f520708 --- /dev/null +++ b/bel/api/core/jwt.py @@ -0,0 +1,69 @@ +# Standard Library +from datetime import datetime, timedelta + +# Third Party +import jwt +from loguru import logger + +# Local +from bel.Config import config + +jwt_algorithm = "HS256" + + +def jwt_create(userid, payload, expiration=None): + """Create a JSON Web Token + payload: dictionary to be added to JWT + expiration: number of seconds from now to expire token -- defaults to 3600 seconds + + """ + + if expiration: + exp = datetime.utcnow() + timedelta(seconds=expiration) + else: + exp = datetime.utcnow() + timedelta(seconds=3600) + + additional_payload = { + "sub": userid, + "exp": exp, + "iat": datetime.utcnow(), + } + + logger.debug("UserId: ", userid, " Payload: ", payload) + + payload.update(additional_payload) + token = jwt.encode( + payload, config["secrets"]["bel_api"]["shared_secret"], algorithm=jwt_algorithm + ) + + return token.decode("utf-8") + + +def jwt_validate(token): + """Validates JSON Web Token + Returns: + valid: boolean - true if valid token + token_payload: dict of token payload + """ + try: + jwt.decode(token, config["secrets"]["bel_api"]["shared_secret"], algorithm=jwt_algorithm) + return True + except Exception as e: + return False + + +def jwt_extract(token): + logger.debug("In JWT Extract") + try: + return ( + jwt.decode( + token, config["secrets"]["bel_api"]["shared_secret"], algorithm=jwt_algorithm + ), + "", + ) + except jwt.ExpiredSignatureError: + logger.debug("JWT expired") + return None, "JWT expired" + except Exception as e: + logger.debug("JWT extraction error ", e) + return None, e diff --git a/bel/api/core/middleware.py b/bel/api/core/middleware.py new file mode 100644 index 0000000..a3fb708 --- /dev/null +++ b/bel/api/core/middleware.py @@ -0,0 +1,43 @@ +# Standard Library +import re +import time + +# Third Party +from loguru import logger +from starlette.middleware.base import BaseHTTPMiddleware + + +@logger.catch +class StatsMiddleware(BaseHTTPMiddleware): + """Get duration of request""" + + async def dispatch(self, request, call_next): + + url = str(request.url) + method = str(request.method) + t0 = time.time() + + response = await call_next(request) + + url = url.rstrip("/") + route_name = url.split("/")[-1] + + # logger.info("Skipping status/metrics", url=url, route_name=route_name) + + if method == "OPTIONS" or route_name in ["status", "metrics", "ping"]: + return response + else: + t1 = time.time() + + duration = f"{(t1 - t0) * 1000:.0f}" + + logger.opt(exception=True).info( + "Request Metrics {duration_ms} ms, status_code: {status_code} {method} {url}", + # "Request Metrics {duration_ms} ms, status_code: {status_code}", + duration_ms=duration, + status_code=response.status_code, + method=method, + url=url, + ) + + return response diff --git a/bel/api/endpoints/bel.py b/bel/api/endpoints/bel.py index aef9a41..70fc086 100644 --- a/bel/api/endpoints/bel.py +++ b/bel/api/endpoints/bel.py @@ -4,11 +4,8 @@ from typing import List, Optional # Third Party -# Third Party Imports import fastapi from fastapi import APIRouter, Depends, Query - -# Local Imports from loguru import logger # Local diff --git a/bel/api/endpoints/belspec.py b/bel/api/endpoints/belspec.py index 6e6c1bc..c4aa7bc 100644 --- a/bel/api/endpoints/belspec.py +++ b/bel/api/endpoints/belspec.py @@ -1,15 +1,15 @@ """belspec endpoints""" -# Third Party Imports + # Third Party import fastapi -from fastapi import APIRouter, Depends, HTTPException, Query +from fastapi import APIRouter, Depends, Query from fastapi.responses import PlainTextResponse from loguru import logger # Local -# Local Imports import bel.belspec.crud +from bel.api.core.exceptions import HTTPException from bel.schemas.belspec import BelSpec, BelSpecVersions, EnhancedBelSpec router = APIRouter() @@ -40,7 +40,9 @@ def get_belspec_version( belspec = bel.belspec.crud.get_enhanced_belspec(version) if not belspec: - raise HTTPException(status_code=404, detail=f"No BEL specification for version {version}") + raise HTTPException( + status_code=404, detail=f"No BEL specification for version {version}", user_flag=True + ) return belspec @@ -61,7 +63,9 @@ def get_enhanced_belspec( if not enhanced_belspec: raise HTTPException( - status_code=404, detail=f"No enhanced BEL specification for version {version}" + status_code=404, + detail=f"No enhanced BEL specification for version {version}", + user_flag=True, ) return enhanced_belspec diff --git a/bel/api/endpoints/info.py b/bel/api/endpoints/info.py index 75f50ec..34e42cc 100644 --- a/bel/api/endpoints/info.py +++ b/bel/api/endpoints/info.py @@ -6,13 +6,11 @@ import re # Third Party -# Third Party Imports import fastapi from fastapi import APIRouter, Depends from loguru import logger # Local -# Local Imports import bel.core.settings as settings import bel.terms.terms from bel.__version__ import __version__ as bel_lib_version diff --git a/bel/api/endpoints/nanopubs.py b/bel/api/endpoints/nanopubs.py index 15f4bc0..f7c6a7b 100644 --- a/bel/api/endpoints/nanopubs.py +++ b/bel/api/endpoints/nanopubs.py @@ -3,14 +3,13 @@ from typing import List # Third Party -# Third Party Imports import fastapi -from fastapi import APIRouter, Depends, HTTPException +from fastapi import APIRouter, Depends from loguru import logger # Local -# Local Imports import bel.nanopub.validate +from bel.api.core.exceptions import HTTPException from bel.schemas.nanopubs import NanopubR router = APIRouter() @@ -33,7 +32,7 @@ def nanopub_validation(nanopub: NanopubR, validation_level: str = "complete"): # data = json.loads(data) if not nanopub: - raise HTTPException(400, detail=f"No nanopub provided") + raise HTTPException(400, detail=f"No nanopub provided", user_flag=True) nanopub = bel.nanopub.validate.validate(nanopub, validation_level=validation_level) diff --git a/bel/api/endpoints/orthology.py b/bel/api/endpoints/orthology.py index da201d4..e0ea6df 100644 --- a/bel/api/endpoints/orthology.py +++ b/bel/api/endpoints/orthology.py @@ -4,13 +4,11 @@ from typing import List # Third Party -# Third Party Imports import fastapi from fastapi import APIRouter, Depends, File, Query, UploadFile from loguru import logger # Local -# Local Imports import bel.terms.orthologs router = APIRouter() diff --git a/bel/api/endpoints/pubmed.py b/bel/api/endpoints/pubmed.py index c485891..54615c4 100644 --- a/bel/api/endpoints/pubmed.py +++ b/bel/api/endpoints/pubmed.py @@ -1,14 +1,14 @@ """pubmed endpoints""" -# Third Party Imports + # Third Party import fastapi -from fastapi import APIRouter, Depends, HTTPException, Query +from fastapi import APIRouter, Depends, Query from loguru import logger # Local -# Local Imports import bel.nanopub.pubmed +from bel.api.core.exceptions import HTTPException router = APIRouter() @@ -25,6 +25,8 @@ def get_pubmed_info( pubmed = bel.nanopub.pubmed.get_pubmed_for_beleditor(pmid, pubmed_only=pubmed_only) if pubmed is None: - raise HTTPException(status_code=404, detail=f"No Pubmed response for {pmid}") + raise HTTPException( + status_code=404, detail=f"No Pubmed response for {pmid}", user_flag=True + ) return pubmed diff --git a/bel/api/endpoints/resources.py b/bel/api/endpoints/resources.py index 2d1336c..1b09936 100644 --- a/bel/api/endpoints/resources.py +++ b/bel/api/endpoints/resources.py @@ -4,16 +4,14 @@ from typing import List, Optional # Third Party -# Third Party Imports import fastapi -from fastapi import APIRouter, Body, Depends, File, HTTPException, Query, UploadFile +from fastapi import APIRouter, Body, Depends, File, Query, UploadFile from loguru import logger # Local import bel.resources.manage - -# Local Imports import bel.terms.terms +from bel.api.core.exceptions import HTTPException from bel.schemas.terms import Term, TermCompletionResponse router = APIRouter() diff --git a/bel/api/endpoints/terms.py b/bel/api/endpoints/terms.py index dfa59ad..53aa0b2 100644 --- a/bel/api/endpoints/terms.py +++ b/bel/api/endpoints/terms.py @@ -4,14 +4,13 @@ from typing import List # Third Party -# Third Party Imports import fastapi -from fastapi import APIRouter, Depends, File, HTTPException, Query, UploadFile +from fastapi import APIRouter, Depends, File, Query, UploadFile from loguru import logger # Local -# Local Imports import bel.terms.terms +from bel.api.core.exceptions import HTTPException from bel.schemas.terms import Term, TermCompletionResponse router = APIRouter() @@ -67,7 +66,7 @@ def get_terms(term_id: str): terms = bel.terms.terms.get_terms(term_id) if not terms: - raise HTTPException(status_code=404, detail=f"Term {term_id} not found") + raise HTTPException(status_code=404, detail=f"Term {term_id} not found", user_flag=True) return terms @@ -79,7 +78,7 @@ def get_term_equivalents(term_id: str): equivalents = bel.terms.terms.get_equivalents(term_id) if not equivalents: - raise HTTPException(status_code=404, detail=f"Term {term_id} not found") + raise HTTPException(status_code=404, detail=f"Term {term_id} not found", user_flag=True) return equivalents diff --git a/bel/belspec/crud.py b/bel/belspec/crud.py index 3918564..9ce15ab 100644 --- a/bel/belspec/crud.py +++ b/bel/belspec/crud.py @@ -4,12 +4,9 @@ # Third Party import cachetools import semver - -# Third Party Imports from loguru import logger # Local -# Local Imports import bel.core.settings as settings import bel.db.arangodb as arangodb from bel.belspec.enhance import create_ebnf_parser, create_enhanced_specification diff --git a/bel/belspec/enhance.py b/bel/belspec/enhance.py index 5868f95..ab0cfe4 100644 --- a/bel/belspec/enhance.py +++ b/bel/belspec/enhance.py @@ -5,11 +5,9 @@ from typing import Any, List, Mapping # Third Party -# Third Party Imports from loguru import logger # Local -# Local Imports import bel.core.settings as settings diff --git a/bel/belspec/specifications.py b/bel/belspec/specifications.py index 4c93641..c0d3b1b 100644 --- a/bel/belspec/specifications.py +++ b/bel/belspec/specifications.py @@ -4,12 +4,9 @@ # Third Party import cachetools - -# Third Party Imports import yaml # Local -# Local Imports import bel.belspec.crud from bel.core.utils import http_client diff --git a/bel/cli/scripts.py b/bel/cli/scripts.py index 2c213ce..de33f6c 100644 --- a/bel/cli/scripts.py +++ b/bel/cli/scripts.py @@ -7,14 +7,11 @@ # Third Party import typer - -# Third Party Imports import yaml from loguru import logger from typer import Argument, Option # Local -# Local Imports import bel.core.settings as settings import bel.core.utils as utils import bel.db.arangodb diff --git a/bel/core/logging_setup.py b/bel/core/logging_setup.py index 2a2c361..9421a47 100644 --- a/bel/core/logging_setup.py +++ b/bel/core/logging_setup.py @@ -1,127 +1,126 @@ -# Local Imports -# Standard Library -import sys - -# Third Party -from loguru import logger - -logger.add( - sys.stderr, - serialize=True, - colorize=True, - format="{time} {level} {file} {line} {message}", - filter="my_module", - level="INFO", -) - - -# # Standard Library -# # from loguru import logger -# import datetime -# from loguru import logger -# import sys - -# # Third Party Imports -# from loguru import logger -# from loguru import logger._frames -# from -# import jsonlogger - -# # Local Imports -# import bel.core.settings as settings - -# """Notes - -# Example on how to setup gunicorn well: http://stevetarver.github.io/2017/05/10/python-falcon-logging.html - -# """ - -# JSON_INDENT = 4 - - -# class CustomJsonFormatter(jsonlogger.JsonFormatter): -# """Customize python json logger to match structlog""" - -# def add_fields(self, log_record, record, message_dict): -# super(CustomJsonFormatter, self).add_fields(log_record, record, message_dict) -# if not log_record.get("timestamp"): -# # this doesn't use record.created, so it is slightly off -# now = datetime.datetime.utcnow().strftime("%Y-%m-%dT%H:%M:%S.%fZ") -# log_record["timestamp"] = now -# if log_record.get("level"): -# log_record["level"] = log_record["level"].upper() -# else: -# log_record["level"] = record.levelname - - -# def add_structlog_app_context(logger, method_name, event_dict): -# f, name = structlog._frames._find_first_app_frame_and_name(["logging", __name__]) -# event_dict["file"] = f.f_code.co_filename -# event_dict["line"] = f.f_lineno -# event_dict["function"] = f.f_code.co_name - -# return event_dict - - -# # Configure Stdlib logging -# formatter = CustomJsonFormatter("(timestamp) (level) (name) (message)", json_indent=JSON_INDENT) -# handler = logging.StreamHandler(sys.stdout) -# handler.setFormatter(formatter) - -# root_logger = logging.getLogger() -# root_logger.addHandler(handler) -# root_logger.setLevel(settings.LOG_LEVEL) - -# bel_logger = logging.getLogger("bel") -# bel_logger.addHandler(handler) -# bel_logger.setLevel(settings.LOG_LEVEL) - -# belapi_logger = logging.getLogger("belapi") -# belapi_logger.addHandler(handler) -# belapi_logger.setLevel(settings.LOG_LEVEL) - -# # Turn off uvicorn access logging by default -# uvicorn_logger = logging.getLogger("uvicorn") -# uvicorn_logger.setLevel("WARNING") - -# uvicornerror_logger = logging.getLogger("uvicorn.error") -# uvicornerror_logger.setLevel("WARNING") - -# uvicornasgi_logger = logging.getLogger("uvicorn.asgi") -# uvicornasgi_logger.setLevel("WARNING") - -# uvicornaccess_logger = logging.getLogger("uvicorn.access") -# uvicornaccess_logger.setLevel("WARNING") - -# elasticsearch_logger = logging.getLogger("elasticsearch") -# elasticsearch_logger.setLevel("WARNING") - -# urllib3_logger = logging.getLogger("urllib3") -# urllib3_logger.setLevel("WARNING") - -# httpx_logger = logging.getLogger("httpx") -# httpx_logger.setLevel("WARNING") - -# fastapi_logger = logging.getLogger("fastapi") -# fastapi_logger.setLevel("WARNING") - -# websockets_logger = logging.getLogger("websockets") -# websockets_logger.setLevel("WARNING") - -# structlog.configure( -# processors=[ -# structlog.stdlib.filter_by_level, -# structlog.stdlib.add_logger_name, -# structlog.stdlib.add_log_level, -# structlog.processors.StackInfoRenderer(), -# structlog.dev.set_exc_info, -# add_structlog_app_context, -# structlog.processors.format_exc_info, -# structlog.processors.TimeStamper(fmt="iso"), -# structlog.stdlib.render_to_log_kwargs, -# ], -# wrapper_class=structlog.stdlib.BoundLogger, -# context_class=dict, # or OrderedDict if the runtime's dict is unordered (e.g. Python <3.6) -# logger_factory=structlog.stdlib.LoggerFactory(), -# cache_logger_on_first_use=True, -# ) +# Standard Library +import sys + +# Third Party +from loguru import logger + +logger.add( + sys.stderr, + serialize=True, + colorize=True, + format="{time} {level} {file} {line} {message}", + filter="my_module", + level="INFO", +) + + +# # Standard Library +# # from loguru import logger +# import datetime +# from loguru import logger +# import sys + +# +# from loguru import logger +# from loguru import logger._frames +# from +# import jsonlogger + +# +# import bel.core.settings as settings + +# """Notes + +# Example on how to setup gunicorn well: http://stevetarver.github.io/2017/05/10/python-falcon-logging.html + +# """ + +# JSON_INDENT = 4 + + +# class CustomJsonFormatter(jsonlogger.JsonFormatter): +# """Customize python json logger to match structlog""" + +# def add_fields(self, log_record, record, message_dict): +# super(CustomJsonFormatter, self).add_fields(log_record, record, message_dict) +# if not log_record.get("timestamp"): +# # this doesn't use record.created, so it is slightly off +# now = datetime.datetime.utcnow().strftime("%Y-%m-%dT%H:%M:%S.%fZ") +# log_record["timestamp"] = now +# if log_record.get("level"): +# log_record["level"] = log_record["level"].upper() +# else: +# log_record["level"] = record.levelname + + +# def add_structlog_app_context(logger, method_name, event_dict): +# f, name = structlog._frames._find_first_app_frame_and_name(["logging", __name__]) +# event_dict["file"] = f.f_code.co_filename +# event_dict["line"] = f.f_lineno +# event_dict["function"] = f.f_code.co_name + +# return event_dict + + +# # Configure Stdlib logging +# formatter = CustomJsonFormatter("(timestamp) (level) (name) (message)", json_indent=JSON_INDENT) +# handler = logging.StreamHandler(sys.stdout) +# handler.setFormatter(formatter) + +# root_logger = logging.getLogger() +# root_logger.addHandler(handler) +# root_logger.setLevel(settings.LOG_LEVEL) + +# bel_logger = logging.getLogger("bel") +# bel_logger.addHandler(handler) +# bel_logger.setLevel(settings.LOG_LEVEL) + +# belapi_logger = logging.getLogger("belapi") +# belapi_logger.addHandler(handler) +# belapi_logger.setLevel(settings.LOG_LEVEL) + +# # Turn off uvicorn access logging by default +# uvicorn_logger = logging.getLogger("uvicorn") +# uvicorn_logger.setLevel("WARNING") + +# uvicornerror_logger = logging.getLogger("uvicorn.error") +# uvicornerror_logger.setLevel("WARNING") + +# uvicornasgi_logger = logging.getLogger("uvicorn.asgi") +# uvicornasgi_logger.setLevel("WARNING") + +# uvicornaccess_logger = logging.getLogger("uvicorn.access") +# uvicornaccess_logger.setLevel("WARNING") + +# elasticsearch_logger = logging.getLogger("elasticsearch") +# elasticsearch_logger.setLevel("WARNING") + +# urllib3_logger = logging.getLogger("urllib3") +# urllib3_logger.setLevel("WARNING") + +# httpx_logger = logging.getLogger("httpx") +# httpx_logger.setLevel("WARNING") + +# fastapi_logger = logging.getLogger("fastapi") +# fastapi_logger.setLevel("WARNING") + +# websockets_logger = logging.getLogger("websockets") +# websockets_logger.setLevel("WARNING") + +# structlog.configure( +# processors=[ +# structlog.stdlib.filter_by_level, +# structlog.stdlib.add_logger_name, +# structlog.stdlib.add_log_level, +# structlog.processors.StackInfoRenderer(), +# structlog.dev.set_exc_info, +# add_structlog_app_context, +# structlog.processors.format_exc_info, +# structlog.processors.TimeStamper(fmt="iso"), +# structlog.stdlib.render_to_log_kwargs, +# ], +# wrapper_class=structlog.stdlib.BoundLogger, +# context_class=dict, # or OrderedDict if the runtime's dict is unordered (e.g. Python <3.6) +# logger_factory=structlog.stdlib.LoggerFactory(), +# cache_logger_on_first_use=True, +# ) diff --git a/bel/core/mail.py b/bel/core/mail.py index 6eae103..5c2c92b 100644 --- a/bel/core/mail.py +++ b/bel/core/mail.py @@ -2,12 +2,10 @@ from typing import List # Third Party -# Third Party Imports import requests from loguru import logger # Local -# Local Imports import bel.core.settings as settings @@ -26,7 +24,7 @@ def send_simple_email(to: List[str], subject: str, body: str, body_html: str = " r = requests.post( f"{settings.MAIL_API}/messages", - auth=("api", settings.MAIL_API_KEY), + auth=("api", settings.MAIL_API_TOKEN), data=data, ) diff --git a/bel/core/settings.py b/bel/core/settings.py index 629ffc0..cdd1c99 100644 --- a/bel/core/settings.py +++ b/bel/core/settings.py @@ -43,7 +43,7 @@ def getenv_boolean(var_name, default=False): # Mailgun settings MAIL_API = os.getenv("MAIL_API", default=None) -MAIL_API_KEY = os.getenv("MAIL_API_KEY") +MAIL_API_TOKEN = os.getenv("MAIL_API_TOKEN") MAIL_FROM = os.getenv("MAIL_FROM") # Auth Settings @@ -130,6 +130,7 @@ def getenv_boolean(var_name, default=False): # Boost these namespaces in term search results and completions BEL_BOOST_NAMESPACES = json.loads(os.getenv("BEL_BOOST_NAMESPACES", default="[]")) + if not BEL_BOOST_NAMESPACES: BEL_BOOST_NAMESPACES = ["HGNC", "MGI", "RGD", "ZFIN", "CHEBI", "GO", "TAX"] diff --git a/bel/core/utils.py b/bel/core/utils.py index e242468..6e04610 100644 --- a/bel/core/utils.py +++ b/bel/core/utils.py @@ -13,11 +13,8 @@ from typing import Any, List, Mapping, Optional, Tuple # Third Party -# Local Imports import dateutil import httpx - -# Third Party Imports import ulid from cityhash import CityHash64 from loguru import logger diff --git a/bel/db/arangodb.py b/bel/db/arangodb.py index c64327e..2222d5c 100644 --- a/bel/db/arangodb.py +++ b/bel/db/arangodb.py @@ -4,7 +4,6 @@ from typing import List, Mapping, Optional # Third Party -# Third Party Imports import arango import arango.client import arango.database @@ -13,7 +12,6 @@ from loguru import logger # Local -# Local Imports import bel.core.settings as settings from bel.core.utils import _create_hash @@ -52,7 +50,9 @@ def get_user_credentials(username, password): def get_client(url=None, port=None, username=None, password=None): """Get arango client db handle""" - url = boltons.iterutils.first([url, settings.ARANGO_URL, "http://localhost:8529"]) + url = boltons.iterutils.first( + [url, settings.ARANGO_URL, "http://localhost:8529"] # DevSkim: ignore DS137138 + ) # DevSkim: ignore DS137138 (username, password) = get_user_credentials(username, password) try: @@ -267,12 +267,12 @@ def get_bel_handles(client, username=None, password=None): if bel_db.has_collection(bel_config_name): bel_config_coll = bel_db.collection(bel_config_name) else: - bel_config_coll = bel_db.create_collection(bel_config_name, index_bucket_count=64) + bel_config_coll = bel_db.create_collection(bel_config_name) if bel_db.has_collection(bel_validations_name): bel_validations_coll = bel_db.collection(bel_validations_name) else: - bel_validations_coll = bel_db.create_collection(bel_validations_name, index_bucket_count=64) + bel_validations_coll = bel_db.create_collection(bel_validations_name) return { "bel_db": bel_db, @@ -350,9 +350,16 @@ def batch_load_docs(db, doc_iterator, on_duplicate: str = "replace"): if counter % batch_size == 0: # logger.debug(f"Bulk import arangodb: {counter}") for collection_name in docs: - collections[collection_name].import_bulk( - docs[collection_name], on_duplicate=on_duplicate, halt_on_error=False - ) + try: + results = collections[collection_name].import_bulk( + docs[collection_name], on_duplicate=on_duplicate, halt_on_error=False + ) + + except Exception as e: + logger.exception( + f"Problem loading arangodb using import_bulk - error: {str(e)}" + ) + docs[collection_name] = [] if counter % 1000000 == 0: @@ -369,6 +376,8 @@ def batch_load_docs(db, doc_iterator, on_duplicate: str = "replace"): except arango.exceptions.DocumentInsertError as e: logger.error(f"Problem with arango bulk import: {str(e)}") + return counter + def arango_id_to_key(_id): """Remove illegal chars from potential arangodb _key (id) or return hashed key if > 60 chars diff --git a/bel/db/elasticsearch.py b/bel/db/elasticsearch.py index fd0995f..aa1d8cc 100644 --- a/bel/db/elasticsearch.py +++ b/bel/db/elasticsearch.py @@ -2,14 +2,12 @@ import os # Third Party -# Third Party Imports import elasticsearch.helpers import yaml from elasticsearch import Elasticsearch from loguru import logger # Local -# Local Imports import bel.core.settings as settings cur_dir_name = os.path.dirname(os.path.realpath(__file__)) diff --git a/bel/db/redis.py b/bel/db/redis.py index 619d9b5..962004f 100644 --- a/bel/db/redis.py +++ b/bel/db/redis.py @@ -3,12 +3,10 @@ from typing import List, Tuple # Third Party -# Third Party Imports import redis from loguru import logger # Local -# Local Imports import bel.core.settings as settings redis_db = redis.Redis(host=settings.REDIS_HOST, port=settings.REDIS_PORT, db=0) diff --git a/bel/lang/ast.py b/bel/lang/ast.py index cc70720..6caff73 100644 --- a/bel/lang/ast.py +++ b/bel/lang/ast.py @@ -11,13 +11,11 @@ from typing import Any, List, Mapping, Optional, Tuple, Union # Third Party -# Third Party Imports import yaml from loguru import logger from pydantic import BaseModel, Field # Local -# Local Imports import bel.core.settings as settings import bel.db.arangodb import bel.terms.orthologs @@ -337,7 +335,7 @@ def print_tree(self, indent=0): else: print("\t" * (indent + 1) + arg.print_tree()) - def subcomponents(self, subcomponents): + def subcomponents(self, subcomponents=None): """Generate subcomponents of the BEL subject or object Args: @@ -348,13 +346,17 @@ def subcomponents(self, subcomponents): List[str]: subcomponents of BEL subject or object """ + if subcomponents is None: + subcomponents = [] + for arg in self.args: if arg.__class__.__name__ == "Function": subcomponents.append(arg.to_string()) - if arg.function_type == "primary": - arg.subcomponents(subcomponents) + arg.subcomponents(subcomponents) else: subcomponents.append(arg.to_string()) + if hasattr(arg, "entity") and arg.entity.nsval.label: + subcomponents.append(f"{arg.entity.nsval.namespace}:{arg.entity.nsval.label}") return subcomponents @@ -363,14 +365,17 @@ def subcomponents(self, subcomponents): # Argument objects # ##################### class Arg(object): - def __init__(self, parent=None, span: Span = None): + def __init__(self, version: str = "latest", parent=None, span: Union[Span, NsArgSpan] = None): self.optional = False self.type = "Arg" + self.version = version self.parent = parent self.siblings = [] + self.belspec = get_enhanced_belspec(self.version) + # https://github.com/belbio/bel/issues/13 # used for sorting arguments (position, modifier, modifier-specific sort parameter) @@ -410,10 +415,11 @@ class NSArg(Arg): """Parsed NSArg value""" def __init__(self, entity: BelEntity, parent=None, span: NsArgSpan = None): - Arg.__init__(self, parent, span) + Arg.__init__(self, parent) self.entity = entity - + self.span: NsArgSpan = span + self.parent = parent self.type = "NSArg" def canonicalize( @@ -493,6 +499,8 @@ def __init__(self, value, span: Span = None, parent=None): Arg.__init__(self, parent, span) self.value = value self.type = "StrArg" + self.span: Span = span + self.parent = parent def update(self, value: str): """Update to new BEL Entity""" @@ -906,6 +914,31 @@ def validate(self): if arg and arg.type in ["Function"]: self.errors.extend(arg.validate(errors=[])) + def subcomponents(self, subcomponents=None): + """Generate subcomponents of the BEL subject or object + + Args: + AST + subcomponents: Pass an empty list to start a new subcomponents request + + Returns: + List[str]: subcomponents of BEL subject or object + """ + + if subcomponents is None: + subcomponents = [] + + for arg in self.args: + if arg.__class__.__name__ == "Function": + subcomponents.append(arg.to_string()) + arg.subcomponents(subcomponents) + else: + subcomponents.append(arg.to_string()) + if hasattr(arg, "entity") and arg.entity.nsval.label: + subcomponents.append(f"{arg.entity.nsval.namespace}:{arg.entity.nsval.label}") + + return subcomponents + def to_string(self, fmt: str = "medium") -> str: """Convert AST object to string diff --git a/bel/lang/belobj.py b/bel/lang/belobj.py index 33b547a..108add5 100644 --- a/bel/lang/belobj.py +++ b/bel/lang/belobj.py @@ -6,11 +6,9 @@ from typing import Any, List, Mapping, Optional, Union # Third Party -# Third Party Imports from loguru import logger # Local -# Local Imports import bel.belspec.crud import bel.core.settings as settings import bel.terms.terms diff --git a/bel/lang/migrate_1_2.py b/bel/lang/migrate_1_2.py index cceb231..a33ee3c 100644 --- a/bel/lang/migrate_1_2.py +++ b/bel/lang/migrate_1_2.py @@ -9,11 +9,9 @@ import json # Third Party -# Third Party Imports from loguru import logger # Local -# Local Imports import bel.belspec.crud import bel.core.settings as settings from bel.belspec.crud import get_enhanced_belspec diff --git a/bel/nanopub/files.py b/bel/nanopub/files.py index e0b4dad..de0b4c4 100644 --- a/bel/nanopub/files.py +++ b/bel/nanopub/files.py @@ -15,7 +15,6 @@ from typing import Any, Iterable, List, Mapping, Tuple # Third Party -# Third Party Imports import click import yaml from loguru import logger diff --git a/bel/nanopub/nanopubs.py b/bel/nanopub/nanopubs.py index 5f8cdd9..793d349 100644 --- a/bel/nanopub/nanopubs.py +++ b/bel/nanopub/nanopubs.py @@ -4,12 +4,9 @@ # Third Party from cityhash import CityHash64 - -# Third Party Imports from loguru import logger # Local -# Local Imports import bel.core.settings as settings import bel.lang.belobj from bel.core.utils import http_client diff --git a/bel/nanopub/pubmed.py b/bel/nanopub/pubmed.py index 798545a..bf6b7fa 100755 --- a/bel/nanopub/pubmed.py +++ b/bel/nanopub/pubmed.py @@ -18,13 +18,10 @@ # Third Party import cachetools import httpx - -# Third Party Imports from loguru import logger from lxml import etree # Local -# Local Imports import bel.core.settings as settings import bel.terms.terms from bel.core.utils import http_client, url_path_param_quoting diff --git a/bel/nanopub/validate.py b/bel/nanopub/validate.py index 1c13a8a..5c0f3db 100644 --- a/bel/nanopub/validate.py +++ b/bel/nanopub/validate.py @@ -4,11 +4,9 @@ from typing import List, Tuple # Third Party -# Third Party Imports from loguru import logger # Local -# Local Imports import bel.core.settings as settings import bel.core.utils import bel.lang.belobj diff --git a/bel/resources/manage.py b/bel/resources/manage.py index 15862ba..d46410a 100644 --- a/bel/resources/manage.py +++ b/bel/resources/manage.py @@ -5,13 +5,10 @@ from typing import List # Third Party -# Third Party Imports from loguru import logger # Local import bel.core.mail - -# Local Imports import bel.core.settings as settings import bel.core.utils import bel.db.arangodb as arangodb diff --git a/bel/resources/namespace.py b/bel/resources/namespace.py index f09e854..d1ada0c 100644 --- a/bel/resources/namespace.py +++ b/bel/resources/namespace.py @@ -7,13 +7,11 @@ from typing import IO, Optional # Third Party -# Third Party Imports import cachetools from arango import ArangoError from loguru import logger # Local -# Local Imports import bel.core.mail import bel.core.settings as settings import bel.db.elasticsearch as elasticsearch @@ -98,7 +96,7 @@ def load_terms( result = {"state": "Succeeded", "messages": []} - statistics = { + metadata["statistics"] = { "entities_count": 0, "synonyms_count": 0, "entity_types": defaultdict(int), @@ -118,14 +116,13 @@ def load_terms( namespace = metadata["namespace"] version = metadata["version"] - - ################################################################################ - # Elasticsearch index processing - ################################################################################ es_version = version.replace("T", "").replace("-", "").replace(":", "") index_prefix = f"{settings.TERMS_INDEX}_{namespace.lower()}" index_name = f"{index_prefix}_{es_version}" + ################################################################################ + # Elasticsearch index processing + ################################################################################ # Create index with mapping if force or prior_version != version: elasticsearch.create_terms_index(index_name) @@ -137,11 +134,9 @@ def load_terms( return result - terms_iterator = terms_iterator_for_elasticsearch(f, index_name, statistics) - elasticsearch.bulk_load_docs(terms_iterator) - # Using side effect to get statistics from terms_iterator_for_elasticsearch on purpose - metadata["statistics"] = copy.deepcopy(statistics) + terms_iterator = terms_iterator_for_elasticsearch(f, index_name, metadata) + elasticsearch.bulk_load_docs(terms_iterator) # Remove old namespace index index_names = elasticsearch.get_all_index_names() @@ -228,6 +223,7 @@ def terms_iterator_for_arangodb(f: IO, version: str): term["_key"] = term_db_key term["version"] = version + # Add term record to terms collection yield (terms_coll_name, term) @@ -321,7 +317,7 @@ def terms_iterator_for_arangodb(f: IO, version: str): yield equiv_edge -def terms_iterator_for_elasticsearch(f: IO, index_name: str, statistics: dict): +def terms_iterator_for_elasticsearch(f: IO, index_name: str, metadata: dict): """Add index_name to term documents for bulk load""" species_list = settings.BEL_FILTER_SPECIES @@ -336,15 +332,15 @@ def terms_iterator_for_elasticsearch(f: IO, index_name: str, statistics: dict): term = term["term"] # Collect statistics - statistics["entities_count"] += 1 - statistics["synonyms_count"] += len(term.get("synonyms", [])) + metadata["statistics"]["entities_count"] += 1 + metadata["statistics"]["synonyms_count"] += len(term.get("synonyms", [])) for entity_type in term.get("entity_types", []): - statistics["entity_types"][entity_type] += 1 + metadata["statistics"]["entity_types"][entity_type] += 1 for annotation_type in term.get("annotation_types", []): - statistics["annotation_types"][annotation_type] += 1 + metadata["statistics"]["annotation_types"][annotation_type] += 1 for equivalence in term.get("equivalence_keys", []): ns, id_ = equivalence.split(":", 1) - statistics["equivalenced_namespaces"][ns] += 1 + metadata["statistics"]["equivalenced_namespaces"][ns] += 1 # Skip if species not listed in config species_list species_key = term.get("species_key", None) diff --git a/bel/resources/ortholog.py b/bel/resources/ortholog.py index 0f23c97..06c091e 100644 --- a/bel/resources/ortholog.py +++ b/bel/resources/ortholog.py @@ -6,12 +6,10 @@ from typing import IO, Mapping, Optional # Third Party -# Third Party Imports from arango import ArangoError from loguru import logger # Local -# Local Imports import bel.core.settings as settings import bel.core.utils import bel.db.arangodb as arangodb diff --git a/bel/schemas/bel.py b/bel/schemas/bel.py index b5accc2..7dc3629 100644 --- a/bel/schemas/bel.py +++ b/bel/schemas/bel.py @@ -6,12 +6,10 @@ from typing import Any, List, Mapping, Optional, Tuple, Union # Third Party -# Third Party Imports from loguru import logger from pydantic import BaseModel, Field, root_validator # Local -# Local Imports import bel.core.settings as settings import bel.db.arangodb import bel.terms.orthologs diff --git a/bel/schemas/belspec.py b/bel/schemas/belspec.py index 360f689..cd23c38 100644 --- a/bel/schemas/belspec.py +++ b/bel/schemas/belspec.py @@ -1,4 +1,3 @@ -# Third Party Imports # Standard Library import enum from typing import Any, List, Mapping, Optional, Union diff --git a/bel/schemas/config.py b/bel/schemas/config.py index ded5077..ac058a5 100644 --- a/bel/schemas/config.py +++ b/bel/schemas/config.py @@ -6,7 +6,6 @@ from typing import Any, List, Mapping, Optional, Tuple, Union # Third Party -# Third Party Imports from pydantic import BaseModel, Field, root_validator diff --git a/bel/schemas/info.py b/bel/schemas/info.py index 5dbceda..013a95f 100644 --- a/bel/schemas/info.py +++ b/bel/schemas/info.py @@ -1,4 +1,3 @@ -# Third Party Imports # Standard Library from typing import Optional, Union @@ -8,12 +7,3 @@ class Version(BaseModel): version: str - - -class Status(BaseModel): - state: str - belapi_version: str - bel_version: str - # settings: dict - # elasticsearch_stats: dict - fastapi_version: str diff --git a/bel/schemas/msg.py b/bel/schemas/msg.py index 52c715b..a626f9a 100644 --- a/bel/schemas/msg.py +++ b/bel/schemas/msg.py @@ -2,7 +2,6 @@ from typing import Optional # Third Party -# Third Party Imports from pydantic import BaseModel diff --git a/bel/schemas/nanopubs.py b/bel/schemas/nanopubs.py index bb44deb..0f55291 100644 --- a/bel/schemas/nanopubs.py +++ b/bel/schemas/nanopubs.py @@ -3,7 +3,6 @@ from typing import Any, List, Mapping, Optional, Union # Third Party -# Third Party Imports import pydantic from pydantic import AnyUrl, BaseModel, Field, HttpUrl, validator @@ -71,7 +70,7 @@ class Metadata(BaseModel): gd_status: Optional[str] gd_createTS: Optional[str] gd_updateTS: Optional[str] - gd_validation: Optional[ValidationErrors] = {} + gd_validation: Optional[ValidationErrors] gd_hash: Optional[str] = Field( "", title="Nanopub hash", diff --git a/bel/schemas/terms.py b/bel/schemas/terms.py index 691b213..cf1008e 100644 --- a/bel/schemas/terms.py +++ b/bel/schemas/terms.py @@ -1,4 +1,3 @@ -# Third Party Imports # Standard Library import enum from typing import Any, List, Mapping, Optional, Union @@ -7,7 +6,6 @@ from pydantic import BaseModel, Field, HttpUrl # Local -# Local Imports from bel.schemas.constants import AnnotationTypesEnum, EntityTypesEnum Key = str # Type alias for NsVal Key values diff --git a/bel/terms/orthologs.py b/bel/terms/orthologs.py index 48c903f..7ecb6f6 100644 --- a/bel/terms/orthologs.py +++ b/bel/terms/orthologs.py @@ -3,12 +3,9 @@ # Third Party import cachetools - -# Third Party Imports from loguru import logger # Local -# Local Imports import bel.db.arangodb import bel.terms.terms from bel.db.arangodb import ortholog_edges_name, ortholog_nodes_name, resources_db diff --git a/bel/terms/terms.py b/bel/terms/terms.py index 9c00e8e..aa051e5 100644 --- a/bel/terms/terms.py +++ b/bel/terms/terms.py @@ -9,7 +9,6 @@ from loguru import logger # Local -# Local Imports import bel.core.settings as settings from bel.core.utils import asyncify, namespace_quoting, split_key_label from bel.db.arangodb import arango_id_to_key, resources_db, terms_coll_name @@ -48,7 +47,7 @@ def get_terms(term_key: Key) -> List[Term]: ) ] - term_key = term_key.replace("'", "\\'") + term_key = term_key.replace("'", "") # Keys can't have single quotes in them-r query = f""" FOR term in {terms_coll_name} FILTER term.key == '{term_key}' OR '{term_key}' in term.alt_keys OR '{term_key}' in term.obsolete_keys @@ -63,7 +62,11 @@ def get_terms(term_key: Key) -> List[Term]: if namespace == "EG": return [] - (namespace, label) = term_key.split(":", 1) + try: + (namespace, label) = term_key.split(":", 1) + except Exception: + return [] # no results - not a valid value + query = f""" for doc in {terms_coll_name} filter doc.namespace == "{namespace}" diff --git a/poetry.lock b/poetry.lock index acd660d..9c0a964 100644 --- a/poetry.lock +++ b/poetry.lock @@ -106,7 +106,7 @@ description = "Python package for providing Mozilla's CA Bundle." name = "certifi" optional = false python-versions = "*" -version = "2020.11.8" +version = "2020.12.5" [[package]] category = "main" @@ -229,7 +229,7 @@ description = "A pure-Python, bring-your-own-I/O implementation of HTTP/1.1" name = "h11" optional = false python-versions = "*" -version = "0.9.0" +version = "0.11.0" [[package]] category = "main" @@ -282,14 +282,14 @@ marker = "python_version >= \"3.5\" and python_version < \"3.8\" or python_versi name = "importlib-metadata" optional = false python-versions = ">=3.6" -version = "3.1.0" +version = "3.1.1" [package.dependencies] zipp = ">=0.5" [package.extras] -docs = ["sphinx", "rst.linker"] -testing = ["packaging", "pep517", "unittest2", "importlib-resources (>=1.3)"] +docs = ["sphinx", "jaraco.packaging (>=3.2)", "rst.linker (>=1.9)"] +testing = ["pytest (>=3.5,<3.7.3 || >3.7.3)", "pytest-checkdocs (>=1.2.3)", "pytest-flake8", "pytest-cov", "jaraco.test (>=3.2.0)", "packaging", "pep517", "pyfakefs", "flufl.flake8", "pytest-black (>=0.3.7)", "pytest-mypy", "importlib-resources (>=1.3)"] [[package]] category = "main" @@ -355,7 +355,7 @@ description = "Powerful and Pythonic XML processing library combining libxml2/li name = "lxml" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, != 3.4.*" -version = "4.6.1" +version = "4.6.2" [package.extras] cssselect = ["cssselect (>=0.7)"] @@ -410,11 +410,10 @@ description = "Core utilities for Python packages" name = "packaging" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" -version = "20.4" +version = "20.7" [package.dependencies] pyparsing = ">=2.0.2" -six = "*" [[package]] category = "dev" @@ -473,7 +472,7 @@ description = "Data validation and settings management using python 3.6 type hin name = "pydantic" optional = false python-versions = ">=3.6" -version = "1.7.2" +version = "1.7.3" [package.extras] dotenv = ["python-dotenv (>=0.10.4)"] @@ -497,7 +496,7 @@ description = "Pygments is a syntax highlighting package written in Python." name = "pygments" optional = false python-versions = ">=3.5" -version = "2.7.2" +version = "2.7.3" [[package]] category = "main" @@ -934,7 +933,7 @@ version = "1.12.1" [[package]] category = "main" description = "Backport of pathlib-compatible object wrapper for zip files" -marker = "python_version >= \"3.5\" and python_version < \"3.8\" or python_version < \"3.8\" or python_version >= \"3.5\" and python_version < \"3.8\" and (python_version >= \"3.5\" and python_version < \"3.8\" or python_version < \"3.8\")" +marker = "python_version >= \"3.5\" and python_version < \"3.8\" or python_version < \"3.8\"" name = "zipp" optional = false python-versions = ">=3.6" @@ -990,8 +989,8 @@ cachetools = [ {file = "cachetools-4.1.1.tar.gz", hash = "sha256:bbaa39c3dede00175df2dc2b03d0cf18dd2d32a7de7beb68072d13043c9edb20"}, ] certifi = [ - {file = "certifi-2020.11.8-py2.py3-none-any.whl", hash = "sha256:1f422849db327d534e3d0c5f02a263458c3955ec0aae4ff09b95f195c59f4edd"}, - {file = "certifi-2020.11.8.tar.gz", hash = "sha256:f05def092c44fbf25834a51509ef6e631dc19765ab8a57b4e7ab85531f0a9cf4"}, + {file = "certifi-2020.12.5-py2.py3-none-any.whl", hash = "sha256:719a74fb9e33b9bd44cc7f3a8d94bc35e4049deebe19ba7d8e108280cfd59830"}, + {file = "certifi-2020.12.5.tar.gz", hash = "sha256:1a4995114262bffbc2413b159f2a1a480c969de6e6eb13ee966d470af86af59c"}, ] chardet = [ {file = "chardet-3.0.4-py2.py3-none-any.whl", hash = "sha256:fc323ffcaeaed0e0a02bf4d117757b98aed530d9ed4531e3e15460124c106691"}, @@ -1065,8 +1064,8 @@ gunicorn = [ {file = "gunicorn-20.0.4.tar.gz", hash = "sha256:1904bb2b8a43658807108d59c3f3d56c2b6121a701161de0ddf9ad140073c626"}, ] h11 = [ - {file = "h11-0.9.0-py2.py3-none-any.whl", hash = "sha256:4bc6d6a1238b7615b266ada57e0618568066f57dd6fa967d1290ec9309b2f2f1"}, - {file = "h11-0.9.0.tar.gz", hash = "sha256:33d4bca7be0fa039f4e84d50ab00531047e53d6ee8ffbc83501ea602c169cae1"}, + {file = "h11-0.11.0-py2.py3-none-any.whl", hash = "sha256:ab6c335e1b6ef34b205d5ca3e228c9299cc7218b049819ec84a388c2525e5d87"}, + {file = "h11-0.11.0.tar.gz", hash = "sha256:3c6c61d69c6f13d41f1b80ab0322f1872702a3ba26e12aa864c928f6a43fbaab"}, ] httpcore = [ {file = "httpcore-0.12.2-py3-none-any.whl", hash = "sha256:420700af11db658c782f7e8fda34f9dcd95e3ee93944dd97d78cb70247e0cd06"}, @@ -1081,8 +1080,8 @@ idna = [ {file = "idna-2.10.tar.gz", hash = "sha256:b307872f855b18632ce0c21c5e45be78c0ea7ae4c15c828c20788b26921eb3f6"}, ] importlib-metadata = [ - {file = "importlib_metadata-3.1.0-py2.py3-none-any.whl", hash = "sha256:590690d61efdd716ff82c39ca9a9d4209252adfe288a4b5721181050acbd4175"}, - {file = "importlib_metadata-3.1.0.tar.gz", hash = "sha256:d9b8a46a0885337627a6430db287176970fff18ad421becec1d64cfc763c2099"}, + {file = "importlib_metadata-3.1.1-py3-none-any.whl", hash = "sha256:6112e21359ef8f344e7178aa5b72dc6e62b38b0d008e6d3cb212c5b84df72013"}, + {file = "importlib_metadata-3.1.1.tar.gz", hash = "sha256:b0c2d3b226157ae4517d9625decf63591461c66b3a808c2666d538946519d170"}, ] iniconfig = [ {file = "iniconfig-1.1.1-py2.py3-none-any.whl", hash = "sha256:011e24c64b7f47f6ebd835bb12a743f2fbe9a26d4cecaa7f53bc4f35ee9da8b3"}, @@ -1124,42 +1123,43 @@ loguru = [ {file = "loguru-0.5.3.tar.gz", hash = "sha256:b28e72ac7a98be3d28ad28570299a393dfcd32e5e3f6a353dec94675767b6319"}, ] lxml = [ - {file = "lxml-4.6.1-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:4b7572145054330c8e324a72d808c8c8fbe12be33368db28c39a255ad5f7fb51"}, - {file = "lxml-4.6.1-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:302160eb6e9764168e01d8c9ec6becddeb87776e81d3fcb0d97954dd51d48e0a"}, - {file = "lxml-4.6.1-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:d4ad7fd3269281cb471ad6c7bafca372e69789540d16e3755dd717e9e5c9d82f"}, - {file = "lxml-4.6.1-cp27-cp27m-win32.whl", hash = "sha256:189ad47203e846a7a4951c17694d845b6ade7917c47c64b29b86526eefc3adf5"}, - {file = "lxml-4.6.1-cp27-cp27m-win_amd64.whl", hash = "sha256:56eff8c6fb7bc4bcca395fdff494c52712b7a57486e4fbde34c31bb9da4c6cc4"}, - {file = "lxml-4.6.1-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:23c83112b4dada0b75789d73f949dbb4e8f29a0a3511647024a398ebd023347b"}, - {file = "lxml-4.6.1-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:0e89f5d422988c65e6936e4ec0fe54d6f73f3128c80eb7ecc3b87f595523607b"}, - {file = "lxml-4.6.1-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:2358809cc64394617f2719147a58ae26dac9e21bae772b45cfb80baa26bfca5d"}, - {file = "lxml-4.6.1-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:be1ebf9cc25ab5399501c9046a7dcdaa9e911802ed0e12b7d620cd4bbf0518b3"}, - {file = "lxml-4.6.1-cp35-cp35m-manylinux2014_aarch64.whl", hash = "sha256:4fff34721b628cce9eb4538cf9a73d02e0f3da4f35a515773cce6f5fe413b360"}, - {file = "lxml-4.6.1-cp35-cp35m-win32.whl", hash = "sha256:475325e037fdf068e0c2140b818518cf6bc4aa72435c407a798b2db9f8e90810"}, - {file = "lxml-4.6.1-cp35-cp35m-win_amd64.whl", hash = "sha256:f98b6f256be6cec8dd308a8563976ddaff0bdc18b730720f6f4bee927ffe926f"}, - {file = "lxml-4.6.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:be7c65e34d1b50ab7093b90427cbc488260e4b3a38ef2435d65b62e9fa3d798a"}, - {file = "lxml-4.6.1-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:d18331ea905a41ae71596502bd4c9a2998902328bbabd29e3d0f5f8569fabad1"}, - {file = "lxml-4.6.1-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:3d9b2b72eb0dbbdb0e276403873ecfae870599c83ba22cadff2db58541e72856"}, - {file = "lxml-4.6.1-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:d20d32cbb31d731def4b1502294ca2ee99f9249b63bc80e03e67e8f8e126dea8"}, - {file = "lxml-4.6.1-cp36-cp36m-win32.whl", hash = "sha256:d182eada8ea0de61a45a526aa0ae4bcd222f9673424e65315c35820291ff299c"}, - {file = "lxml-4.6.1-cp36-cp36m-win_amd64.whl", hash = "sha256:c0dac835c1a22621ffa5e5f999d57359c790c52bbd1c687fe514ae6924f65ef5"}, - {file = "lxml-4.6.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:d84d741c6e35c9f3e7406cb7c4c2e08474c2a6441d59322a00dcae65aac6315d"}, - {file = "lxml-4.6.1-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:8862d1c2c020cb7a03b421a9a7b4fe046a208db30994fc8ff68c627a7915987f"}, - {file = "lxml-4.6.1-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:3a7a380bfecc551cfd67d6e8ad9faa91289173bdf12e9cfafbd2bdec0d7b1ec1"}, - {file = "lxml-4.6.1-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:2d6571c48328be4304aee031d2d5046cbc8aed5740c654575613c5a4f5a11311"}, - {file = "lxml-4.6.1-cp37-cp37m-win32.whl", hash = "sha256:803a80d72d1f693aa448566be46ffd70882d1ad8fc689a2e22afe63035eb998a"}, - {file = "lxml-4.6.1-cp37-cp37m-win_amd64.whl", hash = "sha256:24e811118aab6abe3ce23ff0d7d38932329c513f9cef849d3ee88b0f848f2aa9"}, - {file = "lxml-4.6.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:2e311a10f3e85250910a615fe194839a04a0f6bc4e8e5bb5cac221344e3a7891"}, - {file = "lxml-4.6.1-cp38-cp38-manylinux1_i686.whl", hash = "sha256:a71400b90b3599eb7bf241f947932e18a066907bf84617d80817998cee81e4bf"}, - {file = "lxml-4.6.1-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:211b3bcf5da70c2d4b84d09232534ad1d78320762e2c59dedc73bf01cb1fc45b"}, - {file = "lxml-4.6.1-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:e65c221b2115a91035b55a593b6eb94aa1206fa3ab374f47c6dc10d364583ff9"}, - {file = "lxml-4.6.1-cp38-cp38-win32.whl", hash = "sha256:d6f8c23f65a4bfe4300b85f1f40f6c32569822d08901db3b6454ab785d9117cc"}, - {file = "lxml-4.6.1-cp38-cp38-win_amd64.whl", hash = "sha256:573b2f5496c7e9f4985de70b9bbb4719ffd293d5565513e04ac20e42e6e5583f"}, - {file = "lxml-4.6.1-cp39-cp39-manylinux1_i686.whl", hash = "sha256:1d87936cb5801c557f3e981c9c193861264c01209cb3ad0964a16310ca1b3301"}, - {file = "lxml-4.6.1-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:2d5896ddf5389560257bbe89317ca7bcb4e54a02b53a3e572e1ce4226512b51b"}, - {file = "lxml-4.6.1-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:9b06690224258db5cd39a84e993882a6874676f5de582da57f3df3a82ead9174"}, - {file = "lxml-4.6.1-cp39-cp39-win32.whl", hash = "sha256:bb252f802f91f59767dcc559744e91efa9df532240a502befd874b54571417bd"}, - {file = "lxml-4.6.1-cp39-cp39-win_amd64.whl", hash = "sha256:7ecaef52fd9b9535ae5f01a1dd2651f6608e4ec9dc136fc4dfe7ebe3c3ddb230"}, - {file = "lxml-4.6.1.tar.gz", hash = "sha256:c152b2e93b639d1f36ec5a8ca24cde4a8eefb2b6b83668fcd8e83a67badcb367"}, + {file = "lxml-4.6.2-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:a9d6bc8642e2c67db33f1247a77c53476f3a166e09067c0474facb045756087f"}, + {file = "lxml-4.6.2-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:791394449e98243839fa822a637177dd42a95f4883ad3dec2a0ce6ac99fb0a9d"}, + {file = "lxml-4.6.2-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:68a5d77e440df94011214b7db907ec8f19e439507a70c958f750c18d88f995d2"}, + {file = "lxml-4.6.2-cp27-cp27m-win32.whl", hash = "sha256:fc37870d6716b137e80d19241d0e2cff7a7643b925dfa49b4c8ebd1295eb506e"}, + {file = "lxml-4.6.2-cp27-cp27m-win_amd64.whl", hash = "sha256:69a63f83e88138ab7642d8f61418cf3180a4d8cd13995df87725cb8b893e950e"}, + {file = "lxml-4.6.2-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:42ebca24ba2a21065fb546f3e6bd0c58c3fe9ac298f3a320147029a4850f51a2"}, + {file = "lxml-4.6.2-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:f83d281bb2a6217cd806f4cf0ddded436790e66f393e124dfe9731f6b3fb9afe"}, + {file = "lxml-4.6.2-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:535f067002b0fd1a4e5296a8f1bf88193080ff992a195e66964ef2a6cfec5388"}, + {file = "lxml-4.6.2-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:366cb750140f221523fa062d641393092813b81e15d0e25d9f7c6025f910ee80"}, + {file = "lxml-4.6.2-cp35-cp35m-manylinux2014_aarch64.whl", hash = "sha256:97db258793d193c7b62d4e2586c6ed98d51086e93f9a3af2b2034af01450a74b"}, + {file = "lxml-4.6.2-cp35-cp35m-win32.whl", hash = "sha256:648914abafe67f11be7d93c1a546068f8eff3c5fa938e1f94509e4a5d682b2d8"}, + {file = "lxml-4.6.2-cp35-cp35m-win_amd64.whl", hash = "sha256:4e751e77006da34643ab782e4a5cc21ea7b755551db202bc4d3a423b307db780"}, + {file = "lxml-4.6.2-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:681d75e1a38a69f1e64ab82fe4b1ed3fd758717bed735fb9aeaa124143f051af"}, + {file = "lxml-4.6.2-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:127f76864468d6630e1b453d3ffbbd04b024c674f55cf0a30dc2595137892d37"}, + {file = "lxml-4.6.2-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:4fb85c447e288df535b17ebdebf0ec1cf3a3f1a8eba7e79169f4f37af43c6b98"}, + {file = "lxml-4.6.2-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:5be4a2e212bb6aa045e37f7d48e3e1e4b6fd259882ed5a00786f82e8c37ce77d"}, + {file = "lxml-4.6.2-cp36-cp36m-win32.whl", hash = "sha256:8c88b599e226994ad4db29d93bc149aa1aff3dc3a4355dd5757569ba78632bdf"}, + {file = "lxml-4.6.2-cp36-cp36m-win_amd64.whl", hash = "sha256:6e4183800f16f3679076dfa8abf2db3083919d7e30764a069fb66b2b9eff9939"}, + {file = "lxml-4.6.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:d8d3d4713f0c28bdc6c806a278d998546e8efc3498949e3ace6e117462ac0a5e"}, + {file = "lxml-4.6.2-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:8246f30ca34dc712ab07e51dc34fea883c00b7ccb0e614651e49da2c49a30711"}, + {file = "lxml-4.6.2-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:923963e989ffbceaa210ac37afc9b906acebe945d2723e9679b643513837b089"}, + {file = "lxml-4.6.2-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:1471cee35eba321827d7d53d104e7b8c593ea3ad376aa2df89533ce8e1b24a01"}, + {file = "lxml-4.6.2-cp37-cp37m-win32.whl", hash = "sha256:2363c35637d2d9d6f26f60a208819e7eafc4305ce39dc1d5005eccc4593331c2"}, + {file = "lxml-4.6.2-cp37-cp37m-win_amd64.whl", hash = "sha256:f4822c0660c3754f1a41a655e37cb4dbbc9be3d35b125a37fab6f82d47674ebc"}, + {file = "lxml-4.6.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:0448576c148c129594d890265b1a83b9cd76fd1f0a6a04620753d9a6bcfd0a4d"}, + {file = "lxml-4.6.2-cp38-cp38-manylinux1_i686.whl", hash = "sha256:60a20bfc3bd234d54d49c388950195d23a5583d4108e1a1d47c9eef8d8c042b3"}, + {file = "lxml-4.6.2-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:2e5cc908fe43fe1aa299e58046ad66981131a66aea3129aac7770c37f590a644"}, + {file = "lxml-4.6.2-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:50c348995b47b5a4e330362cf39fc503b4a43b14a91c34c83b955e1805c8e308"}, + {file = "lxml-4.6.2-cp38-cp38-win32.whl", hash = "sha256:94d55bd03d8671686e3f012577d9caa5421a07286dd351dfef64791cf7c6c505"}, + {file = "lxml-4.6.2-cp38-cp38-win_amd64.whl", hash = "sha256:7a7669ff50f41225ca5d6ee0a1ec8413f3a0d8aa2b109f86d540887b7ec0d72a"}, + {file = "lxml-4.6.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:e0bfe9bb028974a481410432dbe1b182e8191d5d40382e5b8ff39cdd2e5c5931"}, + {file = "lxml-4.6.2-cp39-cp39-manylinux1_i686.whl", hash = "sha256:6fd8d5903c2e53f49e99359b063df27fdf7acb89a52b6a12494208bf61345a03"}, + {file = "lxml-4.6.2-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:7e9eac1e526386df7c70ef253b792a0a12dd86d833b1d329e038c7a235dfceb5"}, + {file = "lxml-4.6.2-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:7ee8af0b9f7de635c61cdd5b8534b76c52cd03536f29f51151b377f76e214a1a"}, + {file = "lxml-4.6.2-cp39-cp39-win32.whl", hash = "sha256:2e6fd1b8acd005bd71e6c94f30c055594bbd0aa02ef51a22bbfa961ab63b2d75"}, + {file = "lxml-4.6.2-cp39-cp39-win_amd64.whl", hash = "sha256:535332fe9d00c3cd455bd3dd7d4bacab86e2d564bdf7606079160fa6251caacf"}, + {file = "lxml-4.6.2.tar.gz", hash = "sha256:cd11c7e8d21af997ee8079037fff88f16fda188a9776eb4b81c7e4c9c0a7d7fc"}, ] markupsafe = [ {file = "MarkupSafe-1.1.1-cp27-cp27m-macosx_10_6_intel.whl", hash = "sha256:09027a7803a62ca78792ad89403b1b7a73a01c8cb65909cd876f7fcebd79b161"}, @@ -1216,8 +1216,8 @@ mypy-extensions = [ {file = "mypy_extensions-0.4.3.tar.gz", hash = "sha256:2d82818f5bb3e369420cb3c4060a7970edba416647068eb4c5343488a6c604a8"}, ] packaging = [ - {file = "packaging-20.4-py2.py3-none-any.whl", hash = "sha256:998416ba6962ae7fbd6596850b80e17859a5753ba17c32284f67bfff33784181"}, - {file = "packaging-20.4.tar.gz", hash = "sha256:4357f74f47b9c12db93624a82154e9b120fa8293699949152b22065d556079f8"}, + {file = "packaging-20.7-py2.py3-none-any.whl", hash = "sha256:eb41423378682dadb7166144a4926e443093863024de508ca5c9737d6bc08376"}, + {file = "packaging-20.7.tar.gz", hash = "sha256:05af3bb85d320377db281cf254ab050e1a7ebcbf5410685a9a407e18a1f81236"}, ] pathspec = [ {file = "pathspec-0.8.1-py2.py3-none-any.whl", hash = "sha256:aa0cb481c4041bf52ffa7b0d8fa6cd3e88a2ca4879c533c9153882ee2556790d"}, @@ -1239,36 +1239,36 @@ py = [ {file = "py-1.9.0.tar.gz", hash = "sha256:9ca6883ce56b4e8da7e79ac18787889fa5206c79dcc67fb065376cd2fe03f342"}, ] pydantic = [ - {file = "pydantic-1.7.2-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:dfaa6ed1d509b5aef4142084206584280bb6e9014f01df931ec6febdad5b200a"}, - {file = "pydantic-1.7.2-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:2182ba2a9290964b278bcc07a8d24207de709125d520efec9ad6fa6f92ee058d"}, - {file = "pydantic-1.7.2-cp36-cp36m-manylinux2014_i686.whl", hash = "sha256:0fe8b45d31ae53d74a6aa0bf801587bd49970070eac6a6326f9fa2a302703b8a"}, - {file = "pydantic-1.7.2-cp36-cp36m-manylinux2014_x86_64.whl", hash = "sha256:01f0291f4951580f320f7ae3f2ecaf0044cdebcc9b45c5f882a7e84453362420"}, - {file = "pydantic-1.7.2-cp36-cp36m-win_amd64.whl", hash = "sha256:4ba6b903e1b7bd3eb5df0e78d7364b7e831ed8b4cd781ebc3c4f1077fbcb72a4"}, - {file = "pydantic-1.7.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:b11fc9530bf0698c8014b2bdb3bbc50243e82a7fa2577c8cfba660bcc819e768"}, - {file = "pydantic-1.7.2-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:a3c274c49930dc047a75ecc865e435f3df89715c775db75ddb0186804d9b04d0"}, - {file = "pydantic-1.7.2-cp37-cp37m-manylinux2014_i686.whl", hash = "sha256:c68b5edf4da53c98bb1ccb556ae8f655575cb2e676aef066c12b08c724a3f1a1"}, - {file = "pydantic-1.7.2-cp37-cp37m-manylinux2014_x86_64.whl", hash = "sha256:95d4410c4e429480c736bba0db6cce5aaa311304aea685ebcf9ee47571bfd7c8"}, - {file = "pydantic-1.7.2-cp37-cp37m-win_amd64.whl", hash = "sha256:a2fc7bf77ed4a7a961d7684afe177ff59971828141e608f142e4af858e07dddc"}, - {file = "pydantic-1.7.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:b9572c0db13c8658b4a4cb705dcaae6983aeb9842248b36761b3fbc9010b740f"}, - {file = "pydantic-1.7.2-cp38-cp38-manylinux1_i686.whl", hash = "sha256:f83f679e727742b0c465e7ef992d6da4a7e5268b8edd8fdaf5303276374bef52"}, - {file = "pydantic-1.7.2-cp38-cp38-manylinux2014_i686.whl", hash = "sha256:e5fece30e80087d9b7986104e2ac150647ec1658c4789c89893b03b100ca3164"}, - {file = "pydantic-1.7.2-cp38-cp38-manylinux2014_x86_64.whl", hash = "sha256:ce2d452961352ba229fe1e0b925b41c0c37128f08dddb788d0fd73fd87ea0f66"}, - {file = "pydantic-1.7.2-cp38-cp38-win_amd64.whl", hash = "sha256:fc21a37ff3f545de80b166e1735c4172b41b017948a3fb2d5e2f03c219eac50a"}, - {file = "pydantic-1.7.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:c9760d1556ec59ff745f88269a8f357e2b7afc75c556b3a87b8dda5bc62da8ba"}, - {file = "pydantic-1.7.2-cp39-cp39-manylinux1_i686.whl", hash = "sha256:2c1673633ad1eea78b1c5c420a47cd48717d2ef214c8230d96ca2591e9e00958"}, - {file = "pydantic-1.7.2-cp39-cp39-manylinux2014_i686.whl", hash = "sha256:388c0c26c574ff49bad7d0fd6ed82fbccd86a0473fa3900397d3354c533d6ebb"}, - {file = "pydantic-1.7.2-cp39-cp39-manylinux2014_x86_64.whl", hash = "sha256:ab1d5e4d8de00575957e1c982b951bffaedd3204ddd24694e3baca3332e53a23"}, - {file = "pydantic-1.7.2-cp39-cp39-win_amd64.whl", hash = "sha256:f045cf7afb3352a03bc6cb993578a34560ac24c5d004fa33c76efec6ada1361a"}, - {file = "pydantic-1.7.2-py3-none-any.whl", hash = "sha256:6665f7ab7fbbf4d3c1040925ff4d42d7549a8c15fe041164adfe4fc2134d4cce"}, - {file = "pydantic-1.7.2.tar.gz", hash = "sha256:c8200aecbd1fb914e1bd061d71a4d1d79ecb553165296af0c14989b89e90d09b"}, + {file = "pydantic-1.7.3-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:c59ea046aea25be14dc22d69c97bee629e6d48d2b2ecb724d7fe8806bf5f61cd"}, + {file = "pydantic-1.7.3-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:a4143c8d0c456a093387b96e0f5ee941a950992904d88bc816b4f0e72c9a0009"}, + {file = "pydantic-1.7.3-cp36-cp36m-manylinux2014_i686.whl", hash = "sha256:d8df4b9090b595511906fa48deda47af04e7d092318bfb291f4d45dfb6bb2127"}, + {file = "pydantic-1.7.3-cp36-cp36m-manylinux2014_x86_64.whl", hash = "sha256:514b473d264671a5c672dfb28bdfe1bf1afd390f6b206aa2ec9fed7fc592c48e"}, + {file = "pydantic-1.7.3-cp36-cp36m-win_amd64.whl", hash = "sha256:dba5c1f0a3aeea5083e75db9660935da90216f8a81b6d68e67f54e135ed5eb23"}, + {file = "pydantic-1.7.3-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:59e45f3b694b05a69032a0d603c32d453a23f0de80844fb14d55ab0c6c78ff2f"}, + {file = "pydantic-1.7.3-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:5b24e8a572e4b4c18f614004dda8c9f2c07328cb5b6e314d6e1bbd536cb1a6c1"}, + {file = "pydantic-1.7.3-cp37-cp37m-manylinux2014_i686.whl", hash = "sha256:b2b054d095b6431cdda2f852a6d2f0fdec77686b305c57961b4c5dd6d863bf3c"}, + {file = "pydantic-1.7.3-cp37-cp37m-manylinux2014_x86_64.whl", hash = "sha256:025bf13ce27990acc059d0c5be46f416fc9b293f45363b3d19855165fee1874f"}, + {file = "pydantic-1.7.3-cp37-cp37m-win_amd64.whl", hash = "sha256:6e3874aa7e8babd37b40c4504e3a94cc2023696ced5a0500949f3347664ff8e2"}, + {file = "pydantic-1.7.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:e682f6442ebe4e50cb5e1cfde7dda6766fb586631c3e5569f6aa1951fd1a76ef"}, + {file = "pydantic-1.7.3-cp38-cp38-manylinux1_i686.whl", hash = "sha256:185e18134bec5ef43351149fe34fda4758e53d05bb8ea4d5928f0720997b79ef"}, + {file = "pydantic-1.7.3-cp38-cp38-manylinux2014_i686.whl", hash = "sha256:f5b06f5099e163295b8ff5b1b71132ecf5866cc6e7f586d78d7d3fd6e8084608"}, + {file = "pydantic-1.7.3-cp38-cp38-manylinux2014_x86_64.whl", hash = "sha256:24ca47365be2a5a3cc3f4a26dcc755bcdc9f0036f55dcedbd55663662ba145ec"}, + {file = "pydantic-1.7.3-cp38-cp38-win_amd64.whl", hash = "sha256:d1fe3f0df8ac0f3a9792666c69a7cd70530f329036426d06b4f899c025aca74e"}, + {file = "pydantic-1.7.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:f6864844b039805add62ebe8a8c676286340ba0c6d043ae5dea24114b82a319e"}, + {file = "pydantic-1.7.3-cp39-cp39-manylinux1_i686.whl", hash = "sha256:ecb54491f98544c12c66ff3d15e701612fc388161fd455242447083350904730"}, + {file = "pydantic-1.7.3-cp39-cp39-manylinux2014_i686.whl", hash = "sha256:ffd180ebd5dd2a9ac0da4e8b995c9c99e7c74c31f985ba090ee01d681b1c4b95"}, + {file = "pydantic-1.7.3-cp39-cp39-manylinux2014_x86_64.whl", hash = "sha256:8d72e814c7821125b16f1553124d12faba88e85405b0864328899aceaad7282b"}, + {file = "pydantic-1.7.3-cp39-cp39-win_amd64.whl", hash = "sha256:475f2fa134cf272d6631072554f845d0630907fce053926ff634cc6bc45bf1af"}, + {file = "pydantic-1.7.3-py3-none-any.whl", hash = "sha256:38be427ea01a78206bcaf9a56f835784afcba9e5b88fbdce33bbbfbcd7841229"}, + {file = "pydantic-1.7.3.tar.gz", hash = "sha256:213125b7e9e64713d16d988d10997dabc6a1f73f3991e1ff8e35ebb1409c7dc9"}, ] pydocstyle = [ {file = "pydocstyle-5.1.1-py3-none-any.whl", hash = "sha256:aca749e190a01726a4fb472dd4ef23b5c9da7b9205c0a7857c06533de13fd678"}, {file = "pydocstyle-5.1.1.tar.gz", hash = "sha256:19b86fa8617ed916776a11cd8bc0197e5b9856d5433b777f51a3defe13075325"}, ] pygments = [ - {file = "Pygments-2.7.2-py3-none-any.whl", hash = "sha256:88a0bbcd659fcb9573703957c6b9cff9fab7295e6e76db54c9d00ae42df32773"}, - {file = "Pygments-2.7.2.tar.gz", hash = "sha256:381985fcc551eb9d37c52088a32914e00517e57f4a21609f48141ba08e193fa0"}, + {file = "Pygments-2.7.3-py3-none-any.whl", hash = "sha256:f275b6c0909e5dafd2d6269a656aa90fa58ebf4a74f8fcf9053195d226b24a08"}, + {file = "Pygments-2.7.3.tar.gz", hash = "sha256:ccf3acacf3782cbed4a989426012f1c535c9a90d3a7fc3f16d231b9372d2b716"}, ] pyjwt = [ {file = "PyJWT-1.7.1-py2.py3-none-any.whl", hash = "sha256:5c6eca3c2940464d106b99ba83b00c6add741c9becaec087fb7ccdefea71350e"}, diff --git a/tests/conftest.py b/tests/conftest.py index 84ef2f8..b324ef8 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -3,7 +3,6 @@ import sys # Third Party -# Local Imports import dotenv import pytest diff --git a/tests/lang/test_ast.py b/tests/lang/test_ast.py index 833f53b..ff05fad 100644 --- a/tests/lang/test_ast.py +++ b/tests/lang/test_ast.py @@ -1,4 +1,3 @@ -# Local Imports # Standard Library import json import pprint @@ -16,7 +15,7 @@ @pytest.mark.skip(msg="Cannot handle escaped quote") -def test_ast_parse(): +def test_ast_parse_escaped_quote(): # Bad quote # assertion = AssertionStr(entire='complex(SCOMP:"Test named\" complex", p(HGNC:"207"!"AKT1 Test), p(HGNC:207!"Test"), loc(nucleus)) increases p(HGNC:EGF) increases p(hgnc : "here I am" ! X)') @@ -825,3 +824,32 @@ def test_ast_canonicalization_2(): print("Canonicalized", ast.to_string()) assert ast.to_string() == expected + + +def test_ast_subcomponents_simple(): + + test_input = "path(DO:0080600!COVID-19)" + assertion = AssertionStr(entire=test_input) + + ast = bel.lang.ast.BELAst(assertion=assertion) + + subcomponents = ast.subcomponents() + + print("Subcomponents", subcomponents) + + assert subcomponents == ["path(DO:0080600!COVID-19)", "DO:0080600!COVID-19", "DO:COVID-19"] + + +def test_ast_subcomponents_complex(): + + test_input = """rxn(reactants(complex(p(HGNC:5241!HSPA8), p(HGNC:6501!LAMP2), p(reactome:R-HSA-9622845.1!"HSP90AA1, HSP90AB1"), loc(GO:0005829!cytosol))), products(complex(p(HGNC:6501!LAMP2), p(reactome:R-HSA-9622845.1!"HSP90AA1, HSP90AB1"), loc(GO:0005765!"lysosomal membrane")), p(HGNC:5241!HSPA8, loc(GO:0005829!cytosol))))""" + + assertion = AssertionStr(entire=test_input) + + ast = bel.lang.ast.BELAst(assertion=assertion) + + subcomponents = ast.subcomponents() + + pprint.pprint(subcomponents) + + assert subcomponents == ["path(DO:0080600!COVID-19)", "DO:0080600!COVID-19", "DO:COVID-19"] diff --git a/tests/lang/test_parse.py b/tests/lang/test_parse.py index a13131a..30dde6e 100644 --- a/tests/lang/test_parse.py +++ b/tests/lang/test_parse.py @@ -1,4 +1,3 @@ -# Local Imports # Third Party import pytest diff --git a/tests/nanopub/test_nanopub_validation.py b/tests/nanopub/test_nanopub_validation.py index 0b1ae7b..2612efd 100644 --- a/tests/nanopub/test_nanopub_validation.py +++ b/tests/nanopub/test_nanopub_validation.py @@ -5,7 +5,6 @@ import pytest # Local -# Local Imports import bel.nanopub.validate from bel.schemas.nanopubs import NanopubR diff --git a/tests/nanopub/test_pubmed.py b/tests/nanopub/test_pubmed.py index 2ca9244..c6b841d 100644 --- a/tests/nanopub/test_pubmed.py +++ b/tests/nanopub/test_pubmed.py @@ -3,7 +3,6 @@ import time # Local -# Local Imports import bel.nanopub.pubmed diff --git a/tests/terms/test_orthologs.py b/tests/terms/test_orthologs.py index b7d80d9..68a7815 100644 --- a/tests/terms/test_orthologs.py +++ b/tests/terms/test_orthologs.py @@ -1,4 +1,3 @@ -# Local Imports # Third Party import pytest diff --git a/tests/terms/test_terms.py b/tests/terms/test_terms.py index a572e2a..c0454d5 100644 --- a/tests/terms/test_terms.py +++ b/tests/terms/test_terms.py @@ -1,4 +1,3 @@ -# Local Imports # Third Party import pytest diff --git a/tests/test_schema.py b/tests/test_schema.py index dde3b65..f976636 100644 --- a/tests/test_schema.py +++ b/tests/test_schema.py @@ -1,4 +1,3 @@ -# Local Imports # Local from bel.schemas.bel import AssertionStr, BelEntity, NsVal