diff --git a/.github/workflows/lint_python.yml b/.github/workflows/lint_python.yml new file mode 100644 index 0000000..4d0fea8 --- /dev/null +++ b/.github/workflows/lint_python.yml @@ -0,0 +1,37 @@ +name: lint_python +on: + push: + branches: ["master"] + pull_request: + branches: ["master"] +jobs: + ruff: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - run: pip install --user ruff + - run: ruff --format=github --target-version=py37 . + + lint_python: + needs: ruff + runs-on: ubuntu-latest +# env: +# ia_sandbox: ${{ secrets.ia_sandbox }} + steps: + - uses: actions/checkout@v3 + - name: Install poetry + run: pipx install poetry + - uses: actions/setup-python@v4 + with: + python-version: 3.x + cache: 'poetry' +# - name: Redis Server in GitHub Actions +# uses: supercharge/redis-github-action@1.4.0 + - run: pip install --upgrade pip wheel + - run: poetry install --with=dev + - run: poetry run black --check . + - run: poetry run codespell src/ tests/ *.md *.py # --ignore-words-list="" --skip="*.css,*.js,*.lock" + - run: mkdir --parents --verbose .mypy_cache + - run: poetry run mypy --ignore-missing-imports --install-types --non-interactive --exclude compareshape.py --exclude shape.py . + - run: poetry run safety check + - run: poetry run pytest . diff --git a/.github/workflows/python-app.yml b/.github/workflows/python-app.yml deleted file mode 100644 index b6f4030..0000000 --- a/.github/workflows/python-app.yml +++ /dev/null @@ -1,50 +0,0 @@ -# This workflow will install Python dependencies, run tests and lint with a single version of Python -# For more information see: https://help.github.com/actions/language-and-framework-guides/using-python-with-github-actions - -name: Python application - -on: - push: - branches: [ master ] - pull_request: - branches: [ master ] - -jobs: - build: - - runs-on: ubuntu-latest - environment: default - steps: - - uses: actions/checkout@v2 - with: - # Disabling shallow clone is recommended for improving relevancy of reporting - fetch-depth: 0 - - name: Set up Python 3.10 - uses: actions/setup-python@v2 - with: - python-version: "3.10" - - name: Install dependencies - run: | - python -m pip install --upgrade pip - pip install flake8 coverage - if [ -f requirements.txt ]; then pip install -r requirements.txt; fi - - name: Lint with flake8 - run: | - # stop the build if there are Python syntax errors or undefined names - flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics - # exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide - flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics - - name: Test with unittest - run: | - coverage run --omit=*/site-packages/* -m unittest discover - coverage xml - coverage report - - uses: actions/upload-artifact@v2 - with: - name: code-coverage - path: htmlcov - - name: SonarCloud Scan - uses: sonarsource/sonarcloud-github-action@master - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 0000000..fa9daee --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,70 @@ +repos: +- repo: local + hooks: +# - id: dead +# name: dead +# entry: dead +# args: +# - "--exclude" +# - "test_data/test_content.py|^deprecated/" # regex separate by "|" +# language: system +# pass_filenames: false +# # types: [python] + + + - id: black + name: black + language: system + entry: black . + types: [python] + + - id: codespell + name: codespell + language: system + entry: codespell +# args: +# - "src/" +# - "tests/" +# - "*.md" +# - "*.py" + # pass_filenames: false + types_or: [python, markdown] + exclude: ^test_data/ + + - id: ruff + name: ruff + language: system + entry: ruff + args: + # Tell ruff to fix sorting of imports + - "--fix" + - "--format=github" + - "--target-version=py37" + - "." + # types: [python] + pass_filenames: false + + # https://jaredkhan.com/blog/mypy-pre-commit + - id: mypy + name: mypy + entry: mypy + language: python + # use your preferred Python version + # language_version: python3.7 + # additional_dependencies: ["mypy==0.790"] + types: [python] + # use require_serial so that script + # is only called once per commit + require_serial: true + exclude: shape.py|compareshape.py + # Print the number of files as a sanity-check + # verbose: true + +# - id: pytest +# name: pytest +# language: system +# entry: pytest +# args: +# # - "--durations=10" +# - "-x" +# pass_filenames: false diff --git a/.pylintrc b/.pylintrc deleted file mode 100644 index c6d73d8..0000000 --- a/.pylintrc +++ /dev/null @@ -1,2 +0,0 @@ -[MASTER] -init-hook="from pylint.config import find_pylintrc; import os, sys; sys.path.append(os.path.dirname(find_pylintrc()))" diff --git a/README.md b/README.md index 027a48c..5174e55 100644 --- a/README.md +++ b/README.md @@ -1,17 +1,121 @@ -# entityshape -An api to compare a wikidata item with an entityschema - -This api is available at http://entityshape.toolforge.org/api. The api requires 3 parameters to return a result as follows: -1. __language__: e.g. _en_ the language to return property names in -2. __entity__: e.g. _Q42_ the wikidata entity to check -3. __entityschema__: e.g. _E14_ the entityschema to check against - -The api returns a json object containing the following: -1. __error__: details of any error which may have occurred -2. __schema__: the entityschema checked against -3. __name__: the display name of the entityschema -4. __validity__: the validity of the schema (currently unused) -5. __properties__: a json object describing the validity of each property in the entity -6. __statements__: a json object describing the validity of each statement in the entity - -This repository also contains the source code for the user script at https://www.wikidata.org/w/User:Teester/EntityShape.js which allow use of this api on wikidata entity pages \ No newline at end of file +# [Entityshape](https://www.wikidata.org/wiki/Q119899931) +A python library to compare a wikidata entity +(item or lexeme) with a +[Wikibase Entity Schema](https://www.wikidata.org/wiki/Wikidata:WikiProject_Schemas). + +Based on https://github.com/Teester/entityshape by Mark Tully +and https://github.com/dpriskorn/PyEntityshape by Dennis Priskorn + +# Features +* compare a given wikidata item with an entityschema and dig into missing properties, too many statement, etc. +* determine whether an item is valid according to a certain schema or not +* support for any Wikibase + +# Limitations +The shape and compareshape classes currently only support: +* cardinality (too many or not enough values) +* whether the property is allowed or not +* whether the value of a statement on a given property is correct/incorrect + +It is still a bit unclear if and how the qualifier validation works. + +Validation of lexemes is still considered experimental. +Feel free to open an issue with a working or non-working example. + +# Installation +Get it from pypi + +`$ pip install pyentityshape` + +# Usage + +## Jupyter Notebooks +Example notebooks with code for validation of multiple items: +[hiking paths](https://public-paws.wmcloud.org/User:So9q/Validating%20a%20group%20of%20items-all-hiking-paths-in-sweden.ipynb) +[campsites](https://public-paws.wmcloud.org/User:So9q/Validating%20a%20group%20of%20items-all-campsites-in-sweden.ipynb) +[shelters](https://public-paws.wmcloud.org/User:So9q/Validating%20a%20group%20of%20items-all-shelters-in-sweden.ipynb) + +## CLI +Example: +``` +# Note that we default to English so the lang parameter here is optional. +# Note that we default to Wikidata so the mediawiki_api_url and wikibase_url parameters here are optional. +e = EntityShape(eid="E1", + entity_id="Q1", + lang="en", + # mediawiki_api_url='http://localhost/api.php', + # wikibase_url='http://wikibase.svc' + ) +result = e.validate_and_get_result() +# Get human readable result +print(result) +"Valid: False\nProperties_without_enough_correct_statements: instance of (P31)" +# Access the data +print(result.properties_without_enough_correct_statements) +"{'P31'}" +``` + +## Validation +The is_valid method on the Result object mimics all red warnings displayed by https://www.wikidata.org/wiki/User:Teester/EntityShape.js + +It currently checks these five conditions that all have to be false for the item to be valid: +1. properties with too many statements found +2. incorrect statements found +3. some required properties are missing +4. properties without enough correct statements found +5. statements with properties that are not allowed found + +## Known working schemas +This library currently only supports a subset of all features in the ShEx specification. + +The following Entity Schemas are known to work: +* [hiking path](https://www.wikidata.org/w/index.php?title=EntitySchema:E375&oldid=1833851062) +* [shelter](https://www.wikidata.org/w/index.php?title=EntitySchema:E398&oldid=1923235264) + +# Background +This library is the glue between libraries like [Wikibase +Integrator](https://github.com/LeMyst/WikibaseIntegrator/) and entityschemas. + +It makes it easy to batch check a whole subset of Wikidata +items against a schema. Nice! + +# TODO +The CompareShape and Shape classes should be rewritten using OOP +and enums to avoid passing strings around because that is not +nice to debug or maintain. + +What do we want to know from the CompareShape class? + +On the property level: +* whether the property is mandatory and present/missing + +On the statement level +* whether the cardinality of values is allowed (min/max) +* whether the value(s) are correct/incorrect + +Cases: +* mandatory property is missing +* optional property is missing (this is not invalidating) +* a property has an incorrect value +* a property has a correct value +* a property has too many values +* a property has not enough values +* ? + +# ShEx Tip +When working on your Entity Schemas the constraints here are nice to know/remember +https://shex.io/shex-primer/#tripleConstraints + +# Thanks +Big thanks to [Myst](https://github.com/LeMyst) and +[Christian Clauss](https://github.com/cclauss) for +advice and help with Ruff to make this better. + +# License +GPLv3+ + +# What I learned +* Forking other peoples undocumented spaghetti code is not much fun. +* I want to find a more reliable validator that support somevalue and novalue +* Pydantic is wonderful yet again it makes working with OOP easy peasy :) +* Ruff is crazy fast and very nice! \ No newline at end of file diff --git a/app.py b/app.py deleted file mode 100644 index 82d3b55..0000000 --- a/app.py +++ /dev/null @@ -1,77 +0,0 @@ -""" -A Flask app to compare entityschema with wikidata items without using SPARQL -""" -from json import JSONDecodeError - -import requests - -from flask import Flask, request, json -from flask_cors import CORS -from requests import Response -from compareshape import CompareShape -from shape import Shape - -app = Flask(__name__) -CORS(app) - - -@app.route("/api") -def data(): - """ - Compares an entityschema with a wikidata item - :return: a response to the query - """ - schema: str = request.args.get("entityschema", type=str) - entity: str = request.args.get("entity", type=str) - if "Lexeme" in entity: - entity = entity[7:] - language: str = request.args.get("language", type=str) - try: - # valid: dict = check_against_pyshexy(schema, entity) - valid: dict = {} - shape: Shape = Shape(schema, language) - comparison: CompareShape = CompareShape(shape.get_schema_shape(), entity, language) - payload: dict = {'schema': schema, - 'name': shape.get_name(), - 'validity': valid, - 'general': comparison.get_general(), - 'properties': comparison.get_properties(), - 'statements': comparison.get_statements(), - 'error': ""} - status: int = 200 - except (AttributeError, TypeError, KeyError, IndexError) as exception: - payload: dict = {'schema': "", - 'name': "", - 'validity': "", - 'general': "", - 'properties': "", - 'statements': "", - 'error': "An error has occurred while translating this schema"} - status = 500 - print(f"Schema: {schema} - {type(exception).__name__}: {exception}") - response: Response = app.response_class(response=json.dumps(payload), - status=status, - mimetype="application/json") - return response - - -def check_against_pyshexy(entityschema: str, entity: str): - """ - Checks the entityschema and item against the pyshexy api - :param entityschema: the entityschema E number to be checked - :param entity: The entity Q number to be checked - :return: the response from pyshexy - """ - json_text: dict - url: str = f"https://tools.wmflabs.org/pyshexy/api?entityschema={entityschema}&entity={entity}" - try: - response: Response = requests.get(url) - json_text = response.json() - except JSONDecodeError as exception: - print(f"{type(exception).__name__}: {exception}") - json_text = {} - return json_text - - -if __name__ == '__main__': - app.run() diff --git a/entityshape/__init__.py b/entityshape/__init__.py new file mode 100644 index 0000000..c2b516f --- /dev/null +++ b/entityshape/__init__.py @@ -0,0 +1,71 @@ +import re +from typing import Any, Dict, Optional + +from pydantic import BaseModel + +from entityshape.exceptions import ApiError, EidError, LangError, QidError +from entityshape.models.compareshape import CompareShape +from entityshape.models.result import Result +from entityshape.models.shape import Shape + + +class EntityShape(BaseModel): + """This class models the entityshape API + It has a default timeout of 10 seconds + + The API currently only support items""" + + entity_id: str = "" # item or lexeme + eid: str = "" # entityshape + lang: str = "en" # language defaults to English + result: Result = Result() + eid_regex = re.compile(r"E\d+") + entity_id_regex = re.compile(r"[QL]\d+") + compare_shape_result: Optional[Dict[str, Any]] = None + wikibase_url: str = "http://www.wikidata.org" + mediawiki_api_url: str = "https://www.wikidata.org/w/api.php" + user_agent: str = "entityshape (https://github.com/dpriskorn/entityshape)" + + def __check_inputs__(self): + if not self.lang: + raise LangError("We only support 2 and 3 letter language codes") + if not self.eid: + raise EidError("We need an entityshape EID") + if not re.match(self.eid_regex, self.eid): + raise EidError("EID has to be E followed by only numbers like this: E100") + if not self.entity_id: + raise QidError("We need an item QID") + if not re.match(self.entity_id_regex, self.entity_id): + raise QidError("QID has to be Q followed by only numbers like this: Q100") + + def validate_and_get_result(self) -> Result: + """This method checks if we got the 3 parameters we need and + gets the results and return them""" + self.__check_inputs__() + self.__validate__() + return self.__parse_result__() + + def __validate__(self): + shape: Shape = Shape(self.eid, self.lang) + comparison: CompareShape = CompareShape( + shape.get_schema_shape(), + self.entity_id, + self.lang, + wikibase_url=self.wikibase_url, + mediawiki_api_url=self.mediawiki_api_url, + ) + self.compare_shape_result = {} + self.compare_shape_result = { + "general": comparison.get_general(), + "properties": comparison.get_properties(), + "statements": comparison.get_statements(), + } + + def __parse_result__(self) -> Result: + if self.compare_shape_result: + self.result = Result(**self.compare_shape_result) + self.result.lang = self.lang + self.result.analyze() + return self.result + else: + return Result() diff --git a/entityshape/enums.py b/entityshape/enums.py new file mode 100644 index 0000000..7aa3179 --- /dev/null +++ b/entityshape/enums.py @@ -0,0 +1,24 @@ +from enum import Enum + + +class Necessity(Enum): + ABSENT = "absent" + OPTIONAL = "optional" + REQUIRED = "required" + + +class PropertyResponse(Enum): + MISSING = "missing" + PRESENT = "present" + INCORRECT = "incorrect" + TOO_MANY_STATEMENTS = "too many statements" + CORRECT = "correct" + NOT_ENOUGH_CORRECT_STATEMENTS = "not enough correct statements" + + +class StatementResponse(Enum): + NOT_IN_SCHEMA = "not in schema" + ALLOWED = "allowed" + INCORRECT = "incorrect" + CORRECT = "correct" + MISSING = "missing" diff --git a/entityshape/exceptions.py b/entityshape/exceptions.py new file mode 100644 index 0000000..60428eb --- /dev/null +++ b/entityshape/exceptions.py @@ -0,0 +1,22 @@ +class LangError(BaseException): + pass + + +class QidError(BaseException): + pass + + +class EidError(BaseException): + pass + + +class ApiError(BaseException): + pass + + +class WikibaseEntitySchemaDownloadError(BaseException): + pass + + +class WikibasePropertiesDownloadError(BaseException): + pass diff --git a/entityshape/models/__init__.py b/entityshape/models/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/compareshape.py b/entityshape/models/compareshape.py similarity index 72% rename from compareshape.py rename to entityshape/models/compareshape.py index b0821d5..2dcc2aa 100644 --- a/compareshape.py +++ b/entityshape/models/compareshape.py @@ -1,10 +1,16 @@ """ +Copyright 2023 Dennis Priskorn +Copyright 2021 Mark Tully Compares a json shape from shape.py with wikidata json """ import requests - from requests import Response +from entityshape.exceptions import ( + WikibaseEntitySchemaDownloadError, + WikibasePropertiesDownloadError, +) + class CompareShape: """ @@ -18,14 +24,24 @@ class CompareShape: :returns properties: a json representation of the conformity of each property in the entity :returns statements: a json representation of the conformity of each statement in the entity """ - def __init__(self, shape: dict, entity: str, language: str): + + def __init__( + self, + shape: dict, + entity: str, + language: str, + mediawiki_api_url: str, + wikibase_url: str, + ): self._entity: str = entity self._shape: dict = shape + self.mediawiki_api_url = mediawiki_api_url + self.wikibase_url = wikibase_url self._property_responses: dict = {} self._get_entity_json() if self._entities["entities"][self._entity]: - self._get_props(self._entities["entities"][self._entity]['claims']) + self._get_props(self._entities["entities"][self._entity]["claims"]) self._get_property_names(language) self._compare_statements() self._compare_properties() @@ -65,7 +81,7 @@ def _compare_statements(self): Compares the statements in the entity to the schema """ statements: dict = {} - claims: dict = self._entities["entities"][self._entity]['claims'] + claims: dict = self._entities["entities"][self._entity]["claims"] for claim in claims: statement_results: list = [] property_statement_results: list = [] @@ -73,9 +89,12 @@ def _compare_statements(self): child: dict = {"property": claim} allowed: str = "not in schema" if claim in self._shape: - allowed, extra, qualifiers, required = \ - self._process_claim_in_shape(claim, statement, child) - allowed = self._process_allowed(allowed, required, qualifiers, extra) + allowed, extra, qualifiers, required = self._process_claim_in_shape( + claim, statement, child + ) + allowed = self._process_allowed( + allowed, required, qualifiers, extra + ) if allowed != "": child["response"] = allowed statements[statement["id"]] = child @@ -96,7 +115,7 @@ def _compare_properties(self): child: dict = {"name": self._names[claim], "necessity": "absent"} if claim in self._shape and "necessity" in self._shape[claim]: child["necessity"] = self._shape[claim]["necessity"] - if claim in self._entities["entities"][self._entity]['claims']: + if claim in self._entities["entities"][self._entity]["claims"]: response = self._process_claim(claim, child) if response != "": child["response"] = response @@ -114,10 +133,7 @@ def _process_claim(self, claim, child): allowed = "present" if claim in self._shape: cardinality = self._assess_cardinality(claim, child) - if cardinality == "correct": - response = allowed - else: - response = cardinality + response = allowed if cardinality == "correct" else cardinality if response == "allowed": response = "correct" return response @@ -135,9 +151,15 @@ def _assess_cardinality(self, claim, child): max_cardinality = True if "extra" in self._shape[claim]: number_of_statements = self._property_responses[claim].count("correct") - if "min" in claim_cardinality and number_of_statements < claim_cardinality["min"]: + if ( + "min" in claim_cardinality + and number_of_statements < claim_cardinality["min"] + ): min_cardinality = False - if "max" in claim_cardinality and number_of_statements > claim_cardinality["max"]: + if ( + "max" in claim_cardinality + and number_of_statements > claim_cardinality["max"] + ): max_cardinality = False if min_cardinality and not max_cardinality: cardinality = "too many statements" @@ -149,9 +171,15 @@ def _get_entity_json(self): """ Downloads the entity from wikidata """ - url: str = f"https://www.wikidata.org/wiki/Special:EntityData/{self._entity}.json" + url: str = f"{self.wikibase_url}/wiki/Special:EntityData/{self._entity}.json" response: Response = requests.get(url) - self._entities = response.json() + if response.status_code == 200: + self._entities = response.json() + else: + raise WikibaseEntitySchemaDownloadError( + f"Got {response.status_code} from {url}. " + f"Please check that the configuration is correct" + ) def _get_props(self, claims: dict): """ @@ -171,18 +199,30 @@ def _get_property_names(self, language: str): Gets the names of properties from wikidata """ self._names: dict = {} - wikidata_property_list: list = [self._props[i * 49:(i + 1) * 49] - for i in range((len(self._props) + 48) // 48)] + wikidata_property_list: list = [ + self._props[i * 49 : (i + 1) * 49] + for i in range((len(self._props) + 48) // 48) + ] for element in wikidata_property_list: required_properties: str = "|".join(element) - url: str = f"https://www.wikidata.org/w/api.php?action=wbgetentities&ids=" \ - f"{required_properties}&props=labels&languages={language}&format=json" + url: str = ( + f"{self.mediawiki_api_url}?action=wbgetentities&ids=" + f"{required_properties}&props=labels&languages={language}&format=json" + ) response: Response = requests.get(url) - json_text: dict = response.json() + if response.status_code == 200: + json_text: dict = response.json() + print(url) + # print(json_text) + else: + raise WikibasePropertiesDownloadError( + f"Got {response.status_code} from {url}" + ) for item in element: try: - self._names[json_text["entities"][item]["id"]] = \ - json_text["entities"][item]["labels"][language]["value"] + self._names[json_text["entities"][item]["id"]] = json_text[ + "entities" + ][item]["labels"][language]["value"] except KeyError: self._names[json_text["entities"][item]["id"]] = "" @@ -199,23 +239,31 @@ def _process_required_in_shape_claim(shape_claim, datavalue): required: str = "" if "required" in shape_claim["required"]: shape_claim_required = shape_claim["required"]["required"] - required_property: str = list(shape_claim_required.keys())[0] + required_property: str = next(iter(list(shape_claim_required.keys()))) required_value: str = shape_claim_required[required_property][0] else: - required_property: str = list(shape_claim["required"].keys())[0] + required_property: str = next(iter(list(shape_claim["required"].keys()))) required_value: str = shape_claim["required"][required_property][0] query_entity: str = datavalue["value"]["id"] - url: str = f"https://www.wikidata.org/w/api.php?action=wbgetclaims" \ - f"&entity={query_entity}&property={required_property}&format=json" + url: str = ( + f"https://www.wikidata.org/w/api.php?action=wbgetclaims" + f"&entity={query_entity}&property={required_property}&format=json" + ) response: Response = requests.get(url) - json_text: dict = response.json() + if response.status_code == 200: + json_text: dict = response.json() + else: + raise WikibaseEntitySchemaDownloadError( + f"Got {response.status_code} from {url}" + ) if required_property in json_text["claims"]: for key in json_text["claims"][required_property]: - if key["mainsnak"]["datavalue"]["value"]["id"] == required_value: - required = "present" - else: - required = "incorrect" + required = ( + "present" + if key["mainsnak"]["datavalue"]["value"]["id"] == required_value + else "incorrect" + ) else: required = "missing" return required @@ -223,18 +271,15 @@ def _process_required_in_shape_claim(shape_claim, datavalue): @staticmethod def _process_allowed(allowed, required, qualifiers, extra): if required == "present": - if qualifiers == "": - allowed = "correct" - else: - allowed = qualifiers + allowed = "correct" if qualifiers == "" else qualifiers if required == "incorrect": - if extra == "extra": - allowed = "allowed" - else: - if qualifiers == "": - allowed = "allowed" - else: - allowed = qualifiers + allowed = ( + "allowed" + if extra == "extra" + else "allowed" + if qualifiers == "" + else qualifiers + ) if allowed == "incorrect" and extra == "extra": allowed = "allowed" if required == "missing": @@ -245,8 +290,7 @@ def _process_allowed(allowed, required, qualifiers, extra): def _process_qualifiers_in_shape_claim(shape_claim, statement): allowed_qualifiers: list = [] for qualifier in shape_claim["qualifiers"]: - if "qualifiers" in statement and \ - qualifier not in statement["qualifiers"]: + if "qualifiers" in statement and qualifier not in statement["qualifiers"]: allowed_qualifiers.append(qualifier) if len(allowed_qualifiers) > 0: qualifiers: str = "missing qualifiers: " + ", ".join(allowed_qualifiers) diff --git a/entityshape/models/property_value.py b/entityshape/models/property_value.py new file mode 100644 index 0000000..d4570e3 --- /dev/null +++ b/entityshape/models/property_value.py @@ -0,0 +1,11 @@ +from typing import Optional + +from pydantic import BaseModel + +from entityshape.enums import Necessity, PropertyResponse + + +class PropertyValue(BaseModel): + name: str = "" + necessity: Necessity + response: Optional[PropertyResponse] = None diff --git a/entityshape/models/result.py b/entityshape/models/result.py new file mode 100644 index 0000000..d1edea1 --- /dev/null +++ b/entityshape/models/result.py @@ -0,0 +1,202 @@ +import logging +from typing import Any, Dict, List, Set + +from pydantic import BaseModel +from wikibaseintegrator import WikibaseIntegrator # type: ignore + +from entityshape.enums import Necessity, PropertyResponse, StatementResponse +from entityshape.models.property_value import PropertyValue +from entityshape.models.result_property import ResultProperty +from entityshape.models.statement_value import StatementValue + +logger = logging.getLogger(__name__) + + +class Result(BaseModel): + general: Dict[Any, Any] = {} + name: str = "" + properties: Dict[str, PropertyValue] = {} + statements: Dict[Any, StatementValue] = {} + missing_properties: Set[str] = set() + required_properties: Set[str] = set() + incorrect_statements: Set[str] = set() + missing_statements: Set[str] = set() + properties_with_too_many_statements: Set[str] = set() + analyzed: bool = False + required_properties_that_are_missing: Set[str] = set() + optional_properties_that_are_missing: Set[str] = set() + properties_without_enough_correct_statements: Set[str] = set() + properties_that_are_not_allowed: Set[str] = set() + statements_with_property_that_is_not_allowed: Set[str] = set() + lang: str = "en" + wikibase_url: str = "http://www.wikidata.org" + mediawiki_api_url: str = "https://www.wikidata.org/w/api.php" + + @property + def some_required_properties_are_missing(self): + return bool(self.required_properties_that_are_missing) + + @property + def properties_with_too_many_statements_found(self): + return bool(self.properties_with_too_many_statements) + + @property + def incorrect_statements_found(self): + return bool(self.incorrect_statements) + + @property + def properties_without_enough_correct_statements_found(self): + return bool(self.properties_without_enough_correct_statements) + + @property + def statements_with_properties_that_are_not_allowed_found(self): + return bool(self.statements_with_property_that_is_not_allowed) + + @property + def is_valid(self) -> bool: + """check if the properties are all allowed, + all required properties are present, + not too many statements, + and none of the statements are incorrect""" + self.analyze() + return bool( + not self.properties_with_too_many_statements_found + and not self.incorrect_statements_found + and not self.some_required_properties_are_missing + and not self.properties_without_enough_correct_statements_found + and not self.statements_with_properties_that_are_not_allowed_found + ) + + @property + def is_empty(self): + return bool(len(self.properties) == 0 and len(self.statements) == 0) + + def analyze(self): + if not self.analyzed: + self.__find_missing_properties__() + self.__find_required_properties__() + self.__find_incorrect_statements__() + self.__find_properties_with_too_many_statements__() + self.__find_properties_with_not_enough_correct_statements__() + self.__find_required_properties_that_are_missing__() + self.__find_optional_properties_that_are_missing__() + self.__find_properties_that_are_not_allowed__() + self.__find_statements_with_property_that_is_not_allowed__() + self.analyzed = True + + def __find_properties_with_too_many_statements__(self): + for property_ in self.properties: + value: PropertyValue = self.properties[property_] + if value.response == PropertyResponse.TOO_MANY_STATEMENTS: + self.properties_with_too_many_statements.add(property_) + + def __find_incorrect_statements__(self): + for statement in self.statements: + value: StatementValue = self.statements[statement] + try: + StatementResponse(value.response) + if value.response == StatementResponse.INCORRECT: + self.incorrect_statements.add(statement) + except ValueError: + # Ignore responses we cannot predict + logger.warning(f"Ignoring statement response: {value.response}") + pass + + def __find_required_properties__(self): + for property_ in self.properties: + value: PropertyValue = self.properties[property_] + if value.necessity == Necessity.REQUIRED: + self.required_properties.add(property_) + + def __find_missing_properties__(self): + for property_ in self.properties: + value: PropertyValue = self.properties[property_] + if value.response == PropertyResponse.MISSING: + self.missing_properties.add(property_) + + def __find_required_properties_that_are_missing__(self): + a = set(self.missing_properties) + b = set(self.required_properties) + # the intersection between these two should be empty + # if all required properties are present + self.required_properties_that_are_missing = a.intersection(b) + + def __find_optional_properties_that_are_missing__(self): + """We calculate using set difference""" + a = set(self.missing_properties) + b = set(self.required_properties) + self.optional_properties_that_are_missing = a.difference(b) + + def __find_properties_with_not_enough_correct_statements__(self): + for property_ in self.properties: + value: PropertyValue = self.properties[property_] + if value.response == PropertyResponse.NOT_ENOUGH_CORRECT_STATEMENTS: + self.properties_without_enough_correct_statements.add(property_) + + def __find_properties_that_are_not_allowed__(self): + for property_ in self.properties: + value: PropertyValue = self.properties[property_] + if value.necessity == Necessity.ABSENT: + self.properties_that_are_not_allowed.add(property_) + + def __find_statements_with_property_that_is_not_allowed__(self): + for statement in self.statements: + value: StatementValue = self.statements[statement] + if value.necessity == Necessity.ABSENT: + self.statements_with_property_that_is_not_allowed.add(value.property) + + def get_properties_as_a_string_with_labels_and_pid( + self, wbi, string_properties: Set[str] + ): + properties: List[ResultProperty] = [] + for property_string in string_properties: + wbi_property = wbi.property.get(property_string) + property_object = ResultProperty( + label=wbi_property.labels.get(language=self.lang).value, + pid=property_string, + ) + properties.append(property_object) + return ", ".join([str(property_) for property_ in properties]) + + def __str__(self): + return self.__repr__() + + def __repr__(self): + """Return the result of the validation as a formatted string + with output exactly describing what caused the validation to fail + + We lookup the labels by default. + If no language is set we fall back to English""" + string = f"Valid: {self.is_valid}" + if self.is_valid: + return string + else: + from wikibaseintegrator.wbi_config import config as wbi_config # type: ignore + + # Update the WBI config with the values from the entityshape config + # This enables support for any Wikibase + wbi_config["MEDIAWIKI_API_URL"] = self.mediawiki_api_url + wbi_config["WIKIBASE_URL"] = self.wikibase_url + wbi = WikibaseIntegrator() + if self.properties_with_too_many_statements_found: + properties_string = self.get_properties_as_a_string_with_labels_and_pid( + string_properties=self.properties_with_too_many_statements, wbi=wbi + ) + string += f"\nProperties with too many statements: {properties_string}" + if self.properties_without_enough_correct_statements_found: + properties_string = self.get_properties_as_a_string_with_labels_and_pid( + string_properties=self.properties_without_enough_correct_statements, + wbi=wbi, + ) + string += ( + f"\nProperties without enough correct statements: " + f"{properties_string}" + ) + if self.required_properties_that_are_missing: + properties_string = self.get_properties_as_a_string_with_labels_and_pid( + string_properties=self.required_properties_that_are_missing, wbi=wbi + ) + string += f"\nRequired properties that are missing: {properties_string}" + if self.incorrect_statements: + string += f"\nIncorrect statements: {self.incorrect_statements}" + return string diff --git a/entityshape/models/result_property.py b/entityshape/models/result_property.py new file mode 100644 index 0000000..205abf1 --- /dev/null +++ b/entityshape/models/result_property.py @@ -0,0 +1,9 @@ +from pydantic import BaseModel + + +class ResultProperty(BaseModel): + label: str = "" + pid: str = "" + + def __str__(self): + return f"{self.label} ({self.pid})" diff --git a/shape.py b/entityshape/models/shape.py similarity index 88% rename from shape.py rename to entityshape/models/shape.py index 4268937..0cd1b27 100644 --- a/shape.py +++ b/entityshape/models/shape.py @@ -1,9 +1,11 @@ """ +Copyright 2021 Mark Tully Converts entityschema to json suitable for comparing with a wikidata item """ import os import re -from typing import Optional, Match, Union, Pattern, Any +from typing import Any, Match, Optional, Pattern, Union + import requests @@ -17,6 +19,7 @@ class Shape: :return name: the name of the entityschema :return shape: a json representation of the entityschema """ + def __init__(self, schema: str, language: str): # self.name: str = "" self.schema_shape: dict = {} @@ -60,8 +63,10 @@ def _translate_schema(self): for key in schema_json: if "shape" in schema_json[key]: schema_json[key] = self._translate_sub_shape(schema_json[key]) - if "required" in schema_json[key] and \ - "required" in schema_json[key]["required"]: + if ( + "required" in schema_json[key] + and "required" in schema_json[key]["required"] + ): schema_json[key]["required"] = schema_json[key]["required"]["required"] self.schema_shape = schema_json @@ -160,7 +165,7 @@ def _strip_schema_comments(self): schema_text: str = "" # remove comments from the schema for line in self._json_text["schemaText"].splitlines(): - head, _, _ = line.partition('# ') + head, _, _ = line.partition("# ") if line.startswith("#"): head = "" schema_text += f"\n{head.strip()}" @@ -173,7 +178,8 @@ def _strip_schema_comments(self): schema_text = schema_text.replace("xsd:string", ".") schema_text = schema_text.replace("xsd:decimal", ".") schema_text = schema_text.replace( - "[ ~ ]", ".") + "[ ~ ]", "." + ) schema_text = schema_text.replace("[ ~ ]", ".") schema_text = os.linesep.join([s for s in schema_text.splitlines() if s]) self._schema_text = schema_text @@ -182,8 +188,9 @@ def _get_default_shape(self): """ Gets the default shape to start at in the schema """ - default_shape_name: Optional[Match[str]] = re.search(r"start.*=.*@<.*>", - self._schema_text, re.IGNORECASE) + default_shape_name: Optional[Match[str]] = re.search( + r"start.*=.*@<.*>", self._schema_text, re.IGNORECASE + ) if default_shape_name is not None: default_name: str = default_shape_name.group(0).replace(" ", "") self._default_shape_name = default_name[8:-1] @@ -198,8 +205,9 @@ def _get_specific_shape(self, shape_name: str): :param shape_name: The name of the shape to be extracted :return: The extracted shape """ - search: Union[Pattern[Union[str, Any]], Pattern] = re.compile(r"<%s>.*\n?([{\[])" - % shape_name) + search: Union[Pattern[Union[str, Any]], Pattern] = re.compile( + r"<%s>.*\n?([{\[])" % shape_name + ) parentheses = self._find_parentheses(self._schema_text) try: shape_index: int = re.search(search, self._schema_text).start() @@ -221,14 +229,14 @@ def _find_parentheses(shape): index_list = {} pop_stack = [] for index, character in enumerate(shape): - if character in ['{', '[']: + if character in ["{", "["]: pop_stack.append(index) - elif character in ['}', ']']: + elif character in ["}", "]"]: if len(pop_stack) == 0: - raise IndexError('Too many } for {') + raise IndexError("Too many } for {") index_list[pop_stack.pop()] = index if len(pop_stack) > 0: - raise IndexError('No matching } for {') + raise IndexError("No matching } for {") return index_list def _translate_sub_shape(self, schema_json: dict): @@ -248,12 +256,13 @@ def _translate_sub_shape(self, schema_json: dict): reference_child: dict = {} for key in sub_shape: if "status" in sub_shape[key]: - qualifier_child, reference_child, schema_json = \ - self._assess_sub_shape_key(sub_shape, - key, - schema_json, - qualifier_child, - reference_child) + ( + qualifier_child, + reference_child, + schema_json, + ) = self._assess_sub_shape_key( + sub_shape, key, schema_json, qualifier_child, reference_child + ) schema_json["qualifiers"] = qualifier_child schema_json["references"] = reference_child return schema_json @@ -316,12 +325,18 @@ def _assess_cardinality(necessity: str, child: dict, cardinality: dict): child["cardinality"] = cardinality if "min" in cardinality and cardinality["min"] > 0: necessity = "required" - if "max" in cardinality and "min" in cardinality \ - and cardinality["max"] == 0 and cardinality["min"] == 0: + if ( + "max" in cardinality + and "min" in cardinality + and cardinality["max"] == 0 + and cardinality["min"] == 0 + ): necessity = "absent" return necessity, child - def _assess_sub_shape_key(self, sub_shape, key, schema_json, qualifier_child, reference_child): + def _assess_sub_shape_key( + self, sub_shape, key, schema_json, qualifier_child, reference_child + ): if "shape" in key: sub_shape_json = self._translate_sub_shape(key) if key["status"] == "statement": diff --git a/entityshape/models/statement_value.py b/entityshape/models/statement_value.py new file mode 100644 index 0000000..780d4ab --- /dev/null +++ b/entityshape/models/statement_value.py @@ -0,0 +1,15 @@ +from typing import Optional + +from pydantic import BaseModel + +from entityshape.enums import Necessity + + +class StatementValue(BaseModel): + """ + Limitation: + response can contain arbitrary strings with missing qualifiers so we cannot predict all possible values :/""" + + necessity: Optional[Necessity] = None + property: str = "" + response: str diff --git a/mypy.ini b/mypy.ini new file mode 100644 index 0000000..1e2306f --- /dev/null +++ b/mypy.ini @@ -0,0 +1,9 @@ +# Global options: + +[mypy] +# plugins = pydantic.mypy +warn_return_any = True +warn_unused_configs = True + +[pydantic-mypy] +# warn_untyped_fields = True diff --git a/poetry.lock b/poetry.lock new file mode 100644 index 0000000..8d546f2 --- /dev/null +++ b/poetry.lock @@ -0,0 +1,1157 @@ +# This file is automatically @generated by Poetry 1.5.1 and should not be changed by hand. + +[[package]] +name = "backoff" +version = "2.2.1" +description = "Function decoration for backoff and retry" +optional = false +python-versions = ">=3.7,<4.0" +files = [ + {file = "backoff-2.2.1-py3-none-any.whl", hash = "sha256:63579f9a0628e06278f7e47b7d7d5b6ce20dc65c5e96a6f3ca99a6adca0396e8"}, + {file = "backoff-2.2.1.tar.gz", hash = "sha256:03f829f5bb1923180821643f8753b0502c3b682293992485b0eef2807afa5cba"}, +] + +[[package]] +name = "black" +version = "22.12.0" +description = "The uncompromising code formatter." +optional = false +python-versions = ">=3.7" +files = [ + {file = "black-22.12.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9eedd20838bd5d75b80c9f5487dbcb06836a43833a37846cf1d8c1cc01cef59d"}, + {file = "black-22.12.0-cp310-cp310-win_amd64.whl", hash = "sha256:159a46a4947f73387b4d83e87ea006dbb2337eab6c879620a3ba52699b1f4351"}, + {file = "black-22.12.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d30b212bffeb1e252b31dd269dfae69dd17e06d92b87ad26e23890f3efea366f"}, + {file = "black-22.12.0-cp311-cp311-win_amd64.whl", hash = "sha256:7412e75863aa5c5411886804678b7d083c7c28421210180d67dfd8cf1221e1f4"}, + {file = "black-22.12.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c116eed0efb9ff870ded8b62fe9f28dd61ef6e9ddd28d83d7d264a38417dcee2"}, + {file = "black-22.12.0-cp37-cp37m-win_amd64.whl", hash = "sha256:1f58cbe16dfe8c12b7434e50ff889fa479072096d79f0a7f25e4ab8e94cd8350"}, + {file = "black-22.12.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:77d86c9f3db9b1bf6761244bc0b3572a546f5fe37917a044e02f3166d5aafa7d"}, + {file = "black-22.12.0-cp38-cp38-win_amd64.whl", hash = "sha256:82d9fe8fee3401e02e79767016b4907820a7dc28d70d137eb397b92ef3cc5bfc"}, + {file = "black-22.12.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:101c69b23df9b44247bd88e1d7e90154336ac4992502d4197bdac35dd7ee3320"}, + {file = "black-22.12.0-cp39-cp39-win_amd64.whl", hash = "sha256:559c7a1ba9a006226f09e4916060982fd27334ae1998e7a38b3f33a37f7a2148"}, + {file = "black-22.12.0-py3-none-any.whl", hash = "sha256:436cc9167dd28040ad90d3b404aec22cedf24a6e4d7de221bec2730ec0c97bcf"}, + {file = "black-22.12.0.tar.gz", hash = "sha256:229351e5a18ca30f447bf724d007f890f97e13af070bb6ad4c0a441cd7596a2f"}, +] + +[package.dependencies] +click = ">=8.0.0" +mypy-extensions = ">=0.4.3" +pathspec = ">=0.9.0" +platformdirs = ">=2" +tomli = {version = ">=1.1.0", markers = "python_full_version < \"3.11.0a7\""} +typing-extensions = {version = ">=3.10.0.0", markers = "python_version < \"3.10\""} + +[package.extras] +colorama = ["colorama (>=0.4.3)"] +d = ["aiohttp (>=3.7.4)"] +jupyter = ["ipython (>=7.8.0)", "tokenize-rt (>=3.2.0)"] +uvloop = ["uvloop (>=0.15.2)"] + +[[package]] +name = "certifi" +version = "2023.5.7" +description = "Python package for providing Mozilla's CA Bundle." +optional = false +python-versions = ">=3.6" +files = [ + {file = "certifi-2023.5.7-py3-none-any.whl", hash = "sha256:c6c2e98f5c7869efca1f8916fed228dd91539f9f1b444c314c06eef02980c716"}, + {file = "certifi-2023.5.7.tar.gz", hash = "sha256:0f0d56dc5a6ad56fd4ba36484d6cc34451e1c6548c61daad8c320169f91eddc7"}, +] + +[[package]] +name = "cfgv" +version = "3.3.1" +description = "Validate configuration and produce human readable error messages." +optional = false +python-versions = ">=3.6.1" +files = [ + {file = "cfgv-3.3.1-py2.py3-none-any.whl", hash = "sha256:c6a0883f3917a037485059700b9e75da2464e6c27051014ad85ba6aaa5884426"}, + {file = "cfgv-3.3.1.tar.gz", hash = "sha256:f5a830efb9ce7a445376bb66ec94c638a9787422f96264c98edc6bdeed8ab736"}, +] + +[[package]] +name = "charset-normalizer" +version = "3.1.0" +description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." +optional = false +python-versions = ">=3.7.0" +files = [ + {file = "charset-normalizer-3.1.0.tar.gz", hash = "sha256:34e0a2f9c370eb95597aae63bf85eb5e96826d81e3dcf88b8886012906f509b5"}, + {file = "charset_normalizer-3.1.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:e0ac8959c929593fee38da1c2b64ee9778733cdf03c482c9ff1d508b6b593b2b"}, + {file = "charset_normalizer-3.1.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d7fc3fca01da18fbabe4625d64bb612b533533ed10045a2ac3dd194bfa656b60"}, + {file = "charset_normalizer-3.1.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:04eefcee095f58eaabe6dc3cc2262f3bcd776d2c67005880894f447b3f2cb9c1"}, + {file = "charset_normalizer-3.1.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:20064ead0717cf9a73a6d1e779b23d149b53daf971169289ed2ed43a71e8d3b0"}, + {file = "charset_normalizer-3.1.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1435ae15108b1cb6fffbcea2af3d468683b7afed0169ad718451f8db5d1aff6f"}, + {file = "charset_normalizer-3.1.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c84132a54c750fda57729d1e2599bb598f5fa0344085dbde5003ba429a4798c0"}, + {file = "charset_normalizer-3.1.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:75f2568b4189dda1c567339b48cba4ac7384accb9c2a7ed655cd86b04055c795"}, + {file = "charset_normalizer-3.1.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:11d3bcb7be35e7b1bba2c23beedac81ee893ac9871d0ba79effc7fc01167db6c"}, + {file = "charset_normalizer-3.1.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:891cf9b48776b5c61c700b55a598621fdb7b1e301a550365571e9624f270c203"}, + {file = "charset_normalizer-3.1.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:5f008525e02908b20e04707a4f704cd286d94718f48bb33edddc7d7b584dddc1"}, + {file = "charset_normalizer-3.1.0-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:b06f0d3bf045158d2fb8837c5785fe9ff9b8c93358be64461a1089f5da983137"}, + {file = "charset_normalizer-3.1.0-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:49919f8400b5e49e961f320c735388ee686a62327e773fa5b3ce6721f7e785ce"}, + {file = "charset_normalizer-3.1.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:22908891a380d50738e1f978667536f6c6b526a2064156203d418f4856d6e86a"}, + {file = "charset_normalizer-3.1.0-cp310-cp310-win32.whl", hash = "sha256:12d1a39aa6b8c6f6248bb54550efcc1c38ce0d8096a146638fd4738e42284448"}, + {file = "charset_normalizer-3.1.0-cp310-cp310-win_amd64.whl", hash = "sha256:65ed923f84a6844de5fd29726b888e58c62820e0769b76565480e1fdc3d062f8"}, + {file = "charset_normalizer-3.1.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:9a3267620866c9d17b959a84dd0bd2d45719b817245e49371ead79ed4f710d19"}, + {file = "charset_normalizer-3.1.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6734e606355834f13445b6adc38b53c0fd45f1a56a9ba06c2058f86893ae8017"}, + {file = "charset_normalizer-3.1.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:f8303414c7b03f794347ad062c0516cee0e15f7a612abd0ce1e25caf6ceb47df"}, + {file = "charset_normalizer-3.1.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:aaf53a6cebad0eae578f062c7d462155eada9c172bd8c4d250b8c1d8eb7f916a"}, + {file = "charset_normalizer-3.1.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3dc5b6a8ecfdc5748a7e429782598e4f17ef378e3e272eeb1340ea57c9109f41"}, + {file = "charset_normalizer-3.1.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e1b25e3ad6c909f398df8921780d6a3d120d8c09466720226fc621605b6f92b1"}, + {file = "charset_normalizer-3.1.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0ca564606d2caafb0abe6d1b5311c2649e8071eb241b2d64e75a0d0065107e62"}, + {file = "charset_normalizer-3.1.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b82fab78e0b1329e183a65260581de4375f619167478dddab510c6c6fb04d9b6"}, + {file = "charset_normalizer-3.1.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:bd7163182133c0c7701b25e604cf1611c0d87712e56e88e7ee5d72deab3e76b5"}, + {file = "charset_normalizer-3.1.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:11d117e6c63e8f495412d37e7dc2e2fff09c34b2d09dbe2bee3c6229577818be"}, + {file = "charset_normalizer-3.1.0-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:cf6511efa4801b9b38dc5546d7547d5b5c6ef4b081c60b23e4d941d0eba9cbeb"}, + {file = "charset_normalizer-3.1.0-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:abc1185d79f47c0a7aaf7e2412a0eb2c03b724581139193d2d82b3ad8cbb00ac"}, + {file = "charset_normalizer-3.1.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:cb7b2ab0188829593b9de646545175547a70d9a6e2b63bf2cd87a0a391599324"}, + {file = "charset_normalizer-3.1.0-cp311-cp311-win32.whl", hash = "sha256:c36bcbc0d5174a80d6cccf43a0ecaca44e81d25be4b7f90f0ed7bcfbb5a00909"}, + {file = "charset_normalizer-3.1.0-cp311-cp311-win_amd64.whl", hash = "sha256:cca4def576f47a09a943666b8f829606bcb17e2bc2d5911a46c8f8da45f56755"}, + {file = "charset_normalizer-3.1.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:0c95f12b74681e9ae127728f7e5409cbbef9cd914d5896ef238cc779b8152373"}, + {file = "charset_normalizer-3.1.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fca62a8301b605b954ad2e9c3666f9d97f63872aa4efcae5492baca2056b74ab"}, + {file = "charset_normalizer-3.1.0-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ac0aa6cd53ab9a31d397f8303f92c42f534693528fafbdb997c82bae6e477ad9"}, + {file = "charset_normalizer-3.1.0-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c3af8e0f07399d3176b179f2e2634c3ce9c1301379a6b8c9c9aeecd481da494f"}, + {file = "charset_normalizer-3.1.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3a5fc78f9e3f501a1614a98f7c54d3969f3ad9bba8ba3d9b438c3bc5d047dd28"}, + {file = "charset_normalizer-3.1.0-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:628c985afb2c7d27a4800bfb609e03985aaecb42f955049957814e0491d4006d"}, + {file = "charset_normalizer-3.1.0-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:74db0052d985cf37fa111828d0dd230776ac99c740e1a758ad99094be4f1803d"}, + {file = "charset_normalizer-3.1.0-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:1e8fcdd8f672a1c4fc8d0bd3a2b576b152d2a349782d1eb0f6b8e52e9954731d"}, + {file = "charset_normalizer-3.1.0-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:04afa6387e2b282cf78ff3dbce20f0cc071c12dc8f685bd40960cc68644cfea6"}, + {file = "charset_normalizer-3.1.0-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:dd5653e67b149503c68c4018bf07e42eeed6b4e956b24c00ccdf93ac79cdff84"}, + {file = "charset_normalizer-3.1.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:d2686f91611f9e17f4548dbf050e75b079bbc2a82be565832bc8ea9047b61c8c"}, + {file = "charset_normalizer-3.1.0-cp37-cp37m-win32.whl", hash = "sha256:4155b51ae05ed47199dc5b2a4e62abccb274cee6b01da5b895099b61b1982974"}, + {file = "charset_normalizer-3.1.0-cp37-cp37m-win_amd64.whl", hash = "sha256:322102cdf1ab682ecc7d9b1c5eed4ec59657a65e1c146a0da342b78f4112db23"}, + {file = "charset_normalizer-3.1.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:e633940f28c1e913615fd624fcdd72fdba807bf53ea6925d6a588e84e1151531"}, + {file = "charset_normalizer-3.1.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:3a06f32c9634a8705f4ca9946d667609f52cf130d5548881401f1eb2c39b1e2c"}, + {file = "charset_normalizer-3.1.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:7381c66e0561c5757ffe616af869b916c8b4e42b367ab29fedc98481d1e74e14"}, + {file = "charset_normalizer-3.1.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3573d376454d956553c356df45bb824262c397c6e26ce43e8203c4c540ee0acb"}, + {file = "charset_normalizer-3.1.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e89df2958e5159b811af9ff0f92614dabf4ff617c03a4c1c6ff53bf1c399e0e1"}, + {file = "charset_normalizer-3.1.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:78cacd03e79d009d95635e7d6ff12c21eb89b894c354bd2b2ed0b4763373693b"}, + {file = "charset_normalizer-3.1.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:de5695a6f1d8340b12a5d6d4484290ee74d61e467c39ff03b39e30df62cf83a0"}, + {file = "charset_normalizer-3.1.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1c60b9c202d00052183c9be85e5eaf18a4ada0a47d188a83c8f5c5b23252f649"}, + {file = "charset_normalizer-3.1.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:f645caaf0008bacf349875a974220f1f1da349c5dbe7c4ec93048cdc785a3326"}, + {file = "charset_normalizer-3.1.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:ea9f9c6034ea2d93d9147818f17c2a0860d41b71c38b9ce4d55f21b6f9165a11"}, + {file = "charset_normalizer-3.1.0-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:80d1543d58bd3d6c271b66abf454d437a438dff01c3e62fdbcd68f2a11310d4b"}, + {file = "charset_normalizer-3.1.0-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:73dc03a6a7e30b7edc5b01b601e53e7fc924b04e1835e8e407c12c037e81adbd"}, + {file = "charset_normalizer-3.1.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:6f5c2e7bc8a4bf7c426599765b1bd33217ec84023033672c1e9a8b35eaeaaaf8"}, + {file = "charset_normalizer-3.1.0-cp38-cp38-win32.whl", hash = "sha256:12a2b561af122e3d94cdb97fe6fb2bb2b82cef0cdca131646fdb940a1eda04f0"}, + {file = "charset_normalizer-3.1.0-cp38-cp38-win_amd64.whl", hash = "sha256:3160a0fd9754aab7d47f95a6b63ab355388d890163eb03b2d2b87ab0a30cfa59"}, + {file = "charset_normalizer-3.1.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:38e812a197bf8e71a59fe55b757a84c1f946d0ac114acafaafaf21667a7e169e"}, + {file = "charset_normalizer-3.1.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:6baf0baf0d5d265fa7944feb9f7451cc316bfe30e8df1a61b1bb08577c554f31"}, + {file = "charset_normalizer-3.1.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:8f25e17ab3039b05f762b0a55ae0b3632b2e073d9c8fc88e89aca31a6198e88f"}, + {file = "charset_normalizer-3.1.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3747443b6a904001473370d7810aa19c3a180ccd52a7157aacc264a5ac79265e"}, + {file = "charset_normalizer-3.1.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b116502087ce8a6b7a5f1814568ccbd0e9f6cfd99948aa59b0e241dc57cf739f"}, + {file = "charset_normalizer-3.1.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d16fd5252f883eb074ca55cb622bc0bee49b979ae4e8639fff6ca3ff44f9f854"}, + {file = "charset_normalizer-3.1.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:21fa558996782fc226b529fdd2ed7866c2c6ec91cee82735c98a197fae39f706"}, + {file = "charset_normalizer-3.1.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6f6c7a8a57e9405cad7485f4c9d3172ae486cfef1344b5ddd8e5239582d7355e"}, + {file = "charset_normalizer-3.1.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:ac3775e3311661d4adace3697a52ac0bab17edd166087d493b52d4f4f553f9f0"}, + {file = "charset_normalizer-3.1.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:10c93628d7497c81686e8e5e557aafa78f230cd9e77dd0c40032ef90c18f2230"}, + {file = "charset_normalizer-3.1.0-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:6f4f4668e1831850ebcc2fd0b1cd11721947b6dc7c00bf1c6bd3c929ae14f2c7"}, + {file = "charset_normalizer-3.1.0-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:0be65ccf618c1e7ac9b849c315cc2e8a8751d9cfdaa43027d4f6624bd587ab7e"}, + {file = "charset_normalizer-3.1.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:53d0a3fa5f8af98a1e261de6a3943ca631c526635eb5817a87a59d9a57ebf48f"}, + {file = "charset_normalizer-3.1.0-cp39-cp39-win32.whl", hash = "sha256:a04f86f41a8916fe45ac5024ec477f41f886b3c435da2d4e3d2709b22ab02af1"}, + {file = "charset_normalizer-3.1.0-cp39-cp39-win_amd64.whl", hash = "sha256:830d2948a5ec37c386d3170c483063798d7879037492540f10a475e3fd6f244b"}, + {file = "charset_normalizer-3.1.0-py3-none-any.whl", hash = "sha256:3d9098b479e78c85080c98e1e35ff40b4a31d8953102bb0fd7d1b6f8a2111a3d"}, +] + +[[package]] +name = "click" +version = "8.1.3" +description = "Composable command line interface toolkit" +optional = false +python-versions = ">=3.7" +files = [ + {file = "click-8.1.3-py3-none-any.whl", hash = "sha256:bb4d8133cb15a609f44e8213d9b391b0809795062913b383c62be0ee95b1db48"}, + {file = "click-8.1.3.tar.gz", hash = "sha256:7682dc8afb30297001674575ea00d1814d808d6a36af415a82bd481d37ba7b8e"}, +] + +[package.dependencies] +colorama = {version = "*", markers = "platform_system == \"Windows\""} + +[[package]] +name = "codespell" +version = "2.2.5" +description = "Codespell" +optional = false +python-versions = ">=3.7" +files = [ + {file = "codespell-2.2.5-py3-none-any.whl", hash = "sha256:efa037f54b73c84f7bd14ce8e853d5f822cdd6386ef0ff32e957a3919435b9ec"}, + {file = "codespell-2.2.5.tar.gz", hash = "sha256:6d9faddf6eedb692bf80c9a94ec13ab4f5fb585aabae5f3750727148d7b5be56"}, +] + +[package.extras] +dev = ["Pygments", "build", "chardet", "pytest", "pytest-cov", "pytest-dependency", "ruff", "tomli"] +hard-encoding-detection = ["chardet"] +toml = ["tomli"] +types = ["chardet (>=5.1.0)", "mypy", "pytest", "pytest-cov", "pytest-dependency"] + +[[package]] +name = "colorama" +version = "0.4.6" +description = "Cross-platform colored terminal text." +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" +files = [ + {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, + {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, +] + +[[package]] +name = "coverage" +version = "6.5.0" +description = "Code coverage measurement for Python" +optional = false +python-versions = ">=3.7" +files = [ + {file = "coverage-6.5.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:ef8674b0ee8cc11e2d574e3e2998aea5df5ab242e012286824ea3c6970580e53"}, + {file = "coverage-6.5.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:784f53ebc9f3fd0e2a3f6a78b2be1bd1f5575d7863e10c6e12504f240fd06660"}, + {file = "coverage-6.5.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b4a5be1748d538a710f87542f22c2cad22f80545a847ad91ce45e77417293eb4"}, + {file = "coverage-6.5.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:83516205e254a0cb77d2d7bb3632ee019d93d9f4005de31dca0a8c3667d5bc04"}, + {file = "coverage-6.5.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:af4fffaffc4067232253715065e30c5a7ec6faac36f8fc8d6f64263b15f74db0"}, + {file = "coverage-6.5.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:97117225cdd992a9c2a5515db1f66b59db634f59d0679ca1fa3fe8da32749cae"}, + {file = "coverage-6.5.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:a1170fa54185845505fbfa672f1c1ab175446c887cce8212c44149581cf2d466"}, + {file = "coverage-6.5.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:11b990d520ea75e7ee8dcab5bc908072aaada194a794db9f6d7d5cfd19661e5a"}, + {file = "coverage-6.5.0-cp310-cp310-win32.whl", hash = "sha256:5dbec3b9095749390c09ab7c89d314727f18800060d8d24e87f01fb9cfb40b32"}, + {file = "coverage-6.5.0-cp310-cp310-win_amd64.whl", hash = "sha256:59f53f1dc5b656cafb1badd0feb428c1e7bc19b867479ff72f7a9dd9b479f10e"}, + {file = "coverage-6.5.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:4a5375e28c5191ac38cca59b38edd33ef4cc914732c916f2929029b4bfb50795"}, + {file = "coverage-6.5.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c4ed2820d919351f4167e52425e096af41bfabacb1857186c1ea32ff9983ed75"}, + {file = "coverage-6.5.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:33a7da4376d5977fbf0a8ed91c4dffaaa8dbf0ddbf4c8eea500a2486d8bc4d7b"}, + {file = "coverage-6.5.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a8fb6cf131ac4070c9c5a3e21de0f7dc5a0fbe8bc77c9456ced896c12fcdad91"}, + {file = "coverage-6.5.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:a6b7d95969b8845250586f269e81e5dfdd8ff828ddeb8567a4a2eaa7313460c4"}, + {file = "coverage-6.5.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:1ef221513e6f68b69ee9e159506d583d31aa3567e0ae84eaad9d6ec1107dddaa"}, + {file = "coverage-6.5.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:cca4435eebea7962a52bdb216dec27215d0df64cf27fc1dd538415f5d2b9da6b"}, + {file = "coverage-6.5.0-cp311-cp311-win32.whl", hash = "sha256:98e8a10b7a314f454d9eff4216a9a94d143a7ee65018dd12442e898ee2310578"}, + {file = "coverage-6.5.0-cp311-cp311-win_amd64.whl", hash = "sha256:bc8ef5e043a2af066fa8cbfc6e708d58017024dc4345a1f9757b329a249f041b"}, + {file = "coverage-6.5.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:4433b90fae13f86fafff0b326453dd42fc9a639a0d9e4eec4d366436d1a41b6d"}, + {file = "coverage-6.5.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f4f05d88d9a80ad3cac6244d36dd89a3c00abc16371769f1340101d3cb899fc3"}, + {file = "coverage-6.5.0-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:94e2565443291bd778421856bc975d351738963071e9b8839ca1fc08b42d4bef"}, + {file = "coverage-6.5.0-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:027018943386e7b942fa832372ebc120155fd970837489896099f5cfa2890f79"}, + {file = "coverage-6.5.0-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:255758a1e3b61db372ec2736c8e2a1fdfaf563977eedbdf131de003ca5779b7d"}, + {file = "coverage-6.5.0-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:851cf4ff24062c6aec510a454b2584f6e998cada52d4cb58c5e233d07172e50c"}, + {file = "coverage-6.5.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:12adf310e4aafddc58afdb04d686795f33f4d7a6fa67a7a9d4ce7d6ae24d949f"}, + {file = "coverage-6.5.0-cp37-cp37m-win32.whl", hash = "sha256:b5604380f3415ba69de87a289a2b56687faa4fe04dbee0754bfcae433489316b"}, + {file = "coverage-6.5.0-cp37-cp37m-win_amd64.whl", hash = "sha256:4a8dbc1f0fbb2ae3de73eb0bdbb914180c7abfbf258e90b311dcd4f585d44bd2"}, + {file = "coverage-6.5.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:d900bb429fdfd7f511f868cedd03a6bbb142f3f9118c09b99ef8dc9bf9643c3c"}, + {file = "coverage-6.5.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:2198ea6fc548de52adc826f62cb18554caedfb1d26548c1b7c88d8f7faa8f6ba"}, + {file = "coverage-6.5.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6c4459b3de97b75e3bd6b7d4b7f0db13f17f504f3d13e2a7c623786289dd670e"}, + {file = "coverage-6.5.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:20c8ac5386253717e5ccc827caad43ed66fea0efe255727b1053a8154d952398"}, + {file = "coverage-6.5.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6b07130585d54fe8dff3d97b93b0e20290de974dc8177c320aeaf23459219c0b"}, + {file = "coverage-6.5.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:dbdb91cd8c048c2b09eb17713b0c12a54fbd587d79adcebad543bc0cd9a3410b"}, + {file = "coverage-6.5.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:de3001a203182842a4630e7b8d1a2c7c07ec1b45d3084a83d5d227a3806f530f"}, + {file = "coverage-6.5.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:e07f4a4a9b41583d6eabec04f8b68076ab3cd44c20bd29332c6572dda36f372e"}, + {file = "coverage-6.5.0-cp38-cp38-win32.whl", hash = "sha256:6d4817234349a80dbf03640cec6109cd90cba068330703fa65ddf56b60223a6d"}, + {file = "coverage-6.5.0-cp38-cp38-win_amd64.whl", hash = "sha256:7ccf362abd726b0410bf8911c31fbf97f09f8f1061f8c1cf03dfc4b6372848f6"}, + {file = "coverage-6.5.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:633713d70ad6bfc49b34ead4060531658dc6dfc9b3eb7d8a716d5873377ab745"}, + {file = "coverage-6.5.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:95203854f974e07af96358c0b261f1048d8e1083f2de9b1c565e1be4a3a48cfc"}, + {file = "coverage-6.5.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b9023e237f4c02ff739581ef35969c3739445fb059b060ca51771e69101efffe"}, + {file = "coverage-6.5.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:265de0fa6778d07de30bcf4d9dc471c3dc4314a23a3c6603d356a3c9abc2dfcf"}, + {file = "coverage-6.5.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8f830ed581b45b82451a40faabb89c84e1a998124ee4212d440e9c6cf70083e5"}, + {file = "coverage-6.5.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:7b6be138d61e458e18d8e6ddcddd36dd96215edfe5f1168de0b1b32635839b62"}, + {file = "coverage-6.5.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:42eafe6778551cf006a7c43153af1211c3aaab658d4d66fa5fcc021613d02518"}, + {file = "coverage-6.5.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:723e8130d4ecc8f56e9a611e73b31219595baa3bb252d539206f7bbbab6ffc1f"}, + {file = "coverage-6.5.0-cp39-cp39-win32.whl", hash = "sha256:d9ecf0829c6a62b9b573c7bb6d4dcd6ba8b6f80be9ba4fc7ed50bf4ac9aecd72"}, + {file = "coverage-6.5.0-cp39-cp39-win_amd64.whl", hash = "sha256:fc2af30ed0d5ae0b1abdb4ebdce598eafd5b35397d4d75deb341a614d333d987"}, + {file = "coverage-6.5.0-pp36.pp37.pp38-none-any.whl", hash = "sha256:1431986dac3923c5945271f169f59c45b8802a114c8f548d611f2015133df77a"}, + {file = "coverage-6.5.0.tar.gz", hash = "sha256:f642e90754ee3e06b0e7e51bce3379590e76b7f76b708e1a71ff043f87025c84"}, +] + +[package.extras] +toml = ["tomli"] + +[[package]] +name = "dead" +version = "1.5.2" +description = "dead simple python dead code detection" +optional = false +python-versions = ">=3.8" +files = [ + {file = "dead-1.5.2-py2.py3-none-any.whl", hash = "sha256:4e97c2e4f4c4751b908b56831ab52a34d247570f6ef650c8a34b8b51950540bb"}, + {file = "dead-1.5.2.tar.gz", hash = "sha256:06d82da0adcac7821d45b147c019b3852644f9a23493c0b1578f1c8a7e4e73fd"}, +] + +[package.dependencies] +identify = "*" + +[[package]] +name = "distlib" +version = "0.3.6" +description = "Distribution utilities" +optional = false +python-versions = "*" +files = [ + {file = "distlib-0.3.6-py2.py3-none-any.whl", hash = "sha256:f35c4b692542ca110de7ef0bea44d73981caeb34ca0b9b6b2e6d7790dda8f80e"}, + {file = "distlib-0.3.6.tar.gz", hash = "sha256:14bad2d9b04d3a36127ac97f30b12a19268f211063d8f8ee4f47108896e11b46"}, +] + +[[package]] +name = "dparse" +version = "0.6.2" +description = "A parser for Python dependency files" +optional = false +python-versions = ">=3.5" +files = [ + {file = "dparse-0.6.2-py3-none-any.whl", hash = "sha256:8097076f1dd26c377f30d4745e6ec18fef42f3bf493933b842ac5bafad8c345f"}, + {file = "dparse-0.6.2.tar.gz", hash = "sha256:d45255bda21f998bc7ddf2afd5e62505ba6134756ba2d42a84c56b0826614dfe"}, +] + +[package.dependencies] +packaging = "*" +toml = "*" + +[package.extras] +conda = ["pyyaml"] +pipenv = ["pipenv"] + +[[package]] +name = "exceptiongroup" +version = "1.1.1" +description = "Backport of PEP 654 (exception groups)" +optional = false +python-versions = ">=3.7" +files = [ + {file = "exceptiongroup-1.1.1-py3-none-any.whl", hash = "sha256:232c37c63e4f682982c8b6459f33a8981039e5fb8756b2074364e5055c498c9e"}, + {file = "exceptiongroup-1.1.1.tar.gz", hash = "sha256:d484c3090ba2889ae2928419117447a14daf3c1231d5e30d0aae34f354f01785"}, +] + +[package.extras] +test = ["pytest (>=6)"] + +[[package]] +name = "filelock" +version = "3.12.2" +description = "A platform independent file lock." +optional = false +python-versions = ">=3.7" +files = [ + {file = "filelock-3.12.2-py3-none-any.whl", hash = "sha256:cbb791cdea2a72f23da6ac5b5269ab0a0d161e9ef0100e653b69049a7706d1ec"}, + {file = "filelock-3.12.2.tar.gz", hash = "sha256:002740518d8aa59a26b0c76e10fb8c6e15eae825d34b6fdf670333fd7b938d81"}, +] + +[package.extras] +docs = ["furo (>=2023.5.20)", "sphinx (>=7.0.1)", "sphinx-autodoc-typehints (>=1.23,!=1.23.4)"] +testing = ["covdefaults (>=2.3)", "coverage (>=7.2.7)", "diff-cover (>=7.5)", "pytest (>=7.3.1)", "pytest-cov (>=4.1)", "pytest-mock (>=3.10)", "pytest-timeout (>=2.1)"] + +[[package]] +name = "identify" +version = "2.5.24" +description = "File identification library for Python" +optional = false +python-versions = ">=3.7" +files = [ + {file = "identify-2.5.24-py2.py3-none-any.whl", hash = "sha256:986dbfb38b1140e763e413e6feb44cd731faf72d1909543178aa79b0e258265d"}, + {file = "identify-2.5.24.tar.gz", hash = "sha256:0aac67d5b4812498056d28a9a512a483f5085cc28640b02b258a59dac34301d4"}, +] + +[package.extras] +license = ["ukkonen"] + +[[package]] +name = "idna" +version = "3.4" +description = "Internationalized Domain Names in Applications (IDNA)" +optional = false +python-versions = ">=3.5" +files = [ + {file = "idna-3.4-py3-none-any.whl", hash = "sha256:90b77e79eaa3eba6de819a0c442c0b4ceefc341a7a2ab77d7562bf49f425c5c2"}, + {file = "idna-3.4.tar.gz", hash = "sha256:814f528e8dead7d329833b91c5faa87d60bf71824cd12a7530b5526063d02cb4"}, +] + +[[package]] +name = "iniconfig" +version = "2.0.0" +description = "brain-dead simple config-ini parsing" +optional = false +python-versions = ">=3.7" +files = [ + {file = "iniconfig-2.0.0-py3-none-any.whl", hash = "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374"}, + {file = "iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3"}, +] + +[[package]] +name = "markdown-it-py" +version = "3.0.0" +description = "Python port of markdown-it. Markdown parsing, done right!" +optional = false +python-versions = ">=3.8" +files = [ + {file = "markdown-it-py-3.0.0.tar.gz", hash = "sha256:e3f60a94fa066dc52ec76661e37c851cb232d92f9886b15cb560aaada2df8feb"}, + {file = "markdown_it_py-3.0.0-py3-none-any.whl", hash = "sha256:355216845c60bd96232cd8d8c40e8f9765cc86f46880e43a8fd22dc1a1a8cab1"}, +] + +[package.dependencies] +mdurl = ">=0.1,<1.0" + +[package.extras] +benchmarking = ["psutil", "pytest", "pytest-benchmark"] +code-style = ["pre-commit (>=3.0,<4.0)"] +compare = ["commonmark (>=0.9,<1.0)", "markdown (>=3.4,<4.0)", "mistletoe (>=1.0,<2.0)", "mistune (>=2.0,<3.0)", "panflute (>=2.3,<3.0)"] +linkify = ["linkify-it-py (>=1,<3)"] +plugins = ["mdit-py-plugins"] +profiling = ["gprof2dot"] +rtd = ["jupyter_sphinx", "mdit-py-plugins", "myst-parser", "pyyaml", "sphinx", "sphinx-copybutton", "sphinx-design", "sphinx_book_theme"] +testing = ["coverage", "pytest", "pytest-cov", "pytest-regressions"] + +[[package]] +name = "mdurl" +version = "0.1.2" +description = "Markdown URL utilities" +optional = false +python-versions = ">=3.7" +files = [ + {file = "mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8"}, + {file = "mdurl-0.1.2.tar.gz", hash = "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba"}, +] + +[[package]] +name = "mwoauth" +version = "0.3.8" +description = "A generic MediaWiki OAuth handshake helper." +optional = false +python-versions = "*" +files = [ + {file = "mwoauth-0.3.8-py3-none-any.whl", hash = "sha256:0fe65bc42aa78f29da33df4c851d12e3b6c41820772bb51b18945f7ea710dca2"}, + {file = "mwoauth-0.3.8.tar.gz", hash = "sha256:08daf4edabaa0f95a845f98e5707de7316a8383a9625f20ae768b035991c36ac"}, +] + +[package.dependencies] +oauthlib = "*" +PyJWT = ">=1.0.1" +requests = "*" +requests-oauthlib = "*" +six = "*" + +[package.extras] +flask = ["flask"] + +[[package]] +name = "mypy" +version = "1.4.0" +description = "Optional static typing for Python" +optional = false +python-versions = ">=3.7" +files = [ + {file = "mypy-1.4.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:a3af348e0925a59213244f28c7c0c3a2c2088b4ba2fe9d6c8d4fbb0aba0b7d05"}, + {file = "mypy-1.4.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:a0b2e0da7ff9dd8d2066d093d35a169305fc4e38db378281fce096768a3dbdbf"}, + {file = "mypy-1.4.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:210fe0f39ec5be45dd9d0de253cb79245f0a6f27631d62e0c9c7988be7152965"}, + {file = "mypy-1.4.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:f7a5971490fd4a5a436e143105a1f78fa8b3fe95b30fff2a77542b4f3227a01f"}, + {file = "mypy-1.4.0-cp310-cp310-win_amd64.whl", hash = "sha256:50f65f0e9985f1e50040e603baebab83efed9eb37e15a22a4246fa7cd660f981"}, + {file = "mypy-1.4.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:3b1b5c875fcf3e7217a3de7f708166f641ca154b589664c44a6fd6d9f17d9e7e"}, + {file = "mypy-1.4.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:b4c734d947e761c7ceb1f09a98359dd5666460acbc39f7d0a6b6beec373c5840"}, + {file = "mypy-1.4.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f5984a8d13d35624e3b235a793c814433d810acba9eeefe665cdfed3d08bc3af"}, + {file = "mypy-1.4.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:0f98973e39e4a98709546a9afd82e1ffcc50c6ec9ce6f7870f33ebbf0bd4f26d"}, + {file = "mypy-1.4.0-cp311-cp311-win_amd64.whl", hash = "sha256:19d42b08c7532d736a7e0fb29525855e355fa51fd6aef4f9bbc80749ff64b1a2"}, + {file = "mypy-1.4.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:6ba9a69172abaa73910643744d3848877d6aac4a20c41742027dcfd8d78f05d9"}, + {file = "mypy-1.4.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a34eed094c16cad0f6b0d889811592c7a9b7acf10d10a7356349e325d8704b4f"}, + {file = "mypy-1.4.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:53c2a1fed81e05ded10a4557fe12bae05b9ecf9153f162c662a71d924d504135"}, + {file = "mypy-1.4.0-cp37-cp37m-win_amd64.whl", hash = "sha256:bba57b4d2328740749f676807fcf3036e9de723530781405cc5a5e41fc6e20de"}, + {file = "mypy-1.4.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:653863c75f0dbb687d92eb0d4bd9fe7047d096987ecac93bb7b1bc336de48ebd"}, + {file = "mypy-1.4.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:7461469e163f87a087a5e7aa224102a30f037c11a096a0ceeb721cb0dce274c8"}, + {file = "mypy-1.4.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0cf0ca95e4b8adeaf07815a78b4096b65adf64ea7871b39a2116c19497fcd0dd"}, + {file = "mypy-1.4.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:94a81b9354545123feb1a99b960faeff9e1fa204fce47e0042335b473d71530d"}, + {file = "mypy-1.4.0-cp38-cp38-win_amd64.whl", hash = "sha256:67242d5b28ed0fa88edd8f880aed24da481929467fdbca6487167cb5e3fd31ff"}, + {file = "mypy-1.4.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:3f2b353eebef669529d9bd5ae3566905a685ae98b3af3aad7476d0d519714758"}, + {file = "mypy-1.4.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:62bf18d97c6b089f77f0067b4e321db089d8520cdeefc6ae3ec0f873621c22e5"}, + {file = "mypy-1.4.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ca33ab70a4aaa75bb01086a0b04f0ba8441e51e06fc57e28585176b08cad533b"}, + {file = "mypy-1.4.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:5a0ee54c2cb0f957f8a6f41794d68f1a7e32b9968675ade5846f538504856d42"}, + {file = "mypy-1.4.0-cp39-cp39-win_amd64.whl", hash = "sha256:6c34d43e3d54ad05024576aef28081d9d0580f6fa7f131255f54020eb12f5352"}, + {file = "mypy-1.4.0-py3-none-any.whl", hash = "sha256:f051ca656be0c179c735a4c3193f307d34c92fdc4908d44fd4516fbe8b10567d"}, + {file = "mypy-1.4.0.tar.gz", hash = "sha256:de1e7e68148a213036276d1f5303b3836ad9a774188961eb2684eddff593b042"}, +] + +[package.dependencies] +mypy-extensions = ">=1.0.0" +tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""} +typing-extensions = ">=3.10" + +[package.extras] +dmypy = ["psutil (>=4.0)"] +install-types = ["pip"] +python2 = ["typed-ast (>=1.4.0,<2)"] +reports = ["lxml"] + +[[package]] +name = "mypy-extensions" +version = "1.0.0" +description = "Type system extensions for programs checked with the mypy type checker." +optional = false +python-versions = ">=3.5" +files = [ + {file = "mypy_extensions-1.0.0-py3-none-any.whl", hash = "sha256:4392f6c0eb8a5668a69e23d168ffa70f0be9ccfd32b5cc2d26a34ae5b844552d"}, + {file = "mypy_extensions-1.0.0.tar.gz", hash = "sha256:75dbf8955dc00442a438fc4d0666508a9a97b6bd41aa2f0ffe9d2f2725af0782"}, +] + +[[package]] +name = "nodeenv" +version = "1.8.0" +description = "Node.js virtual environment builder" +optional = false +python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*" +files = [ + {file = "nodeenv-1.8.0-py2.py3-none-any.whl", hash = "sha256:df865724bb3c3adc86b3876fa209771517b0cfe596beff01a92700e0e8be4cec"}, + {file = "nodeenv-1.8.0.tar.gz", hash = "sha256:d51e0c37e64fbf47d017feac3145cdbb58836d7eee8c6f6d3b6880c5456227d2"}, +] + +[package.dependencies] +setuptools = "*" + +[[package]] +name = "oauthlib" +version = "3.2.2" +description = "A generic, spec-compliant, thorough implementation of the OAuth request-signing logic" +optional = false +python-versions = ">=3.6" +files = [ + {file = "oauthlib-3.2.2-py3-none-any.whl", hash = "sha256:8139f29aac13e25d502680e9e19963e83f16838d48a0d71c287fe40e7067fbca"}, + {file = "oauthlib-3.2.2.tar.gz", hash = "sha256:9859c40929662bec5d64f34d01c99e093149682a3f38915dc0655d5a633dd918"}, +] + +[package.extras] +rsa = ["cryptography (>=3.0.0)"] +signals = ["blinker (>=1.4.0)"] +signedtoken = ["cryptography (>=3.0.0)", "pyjwt (>=2.0.0,<3)"] + +[[package]] +name = "packaging" +version = "21.3" +description = "Core utilities for Python packages" +optional = false +python-versions = ">=3.6" +files = [ + {file = "packaging-21.3-py3-none-any.whl", hash = "sha256:ef103e05f519cdc783ae24ea4e2e0f508a9c99b2d4969652eed6a2e1ea5bd522"}, + {file = "packaging-21.3.tar.gz", hash = "sha256:dd47c42927d89ab911e606518907cc2d3a1f38bbd026385970643f9c5b8ecfeb"}, +] + +[package.dependencies] +pyparsing = ">=2.0.2,<3.0.5 || >3.0.5" + +[[package]] +name = "pathspec" +version = "0.11.1" +description = "Utility library for gitignore style pattern matching of file paths." +optional = false +python-versions = ">=3.7" +files = [ + {file = "pathspec-0.11.1-py3-none-any.whl", hash = "sha256:d8af70af76652554bd134c22b3e8a1cc46ed7d91edcdd721ef1a0c51a84a5293"}, + {file = "pathspec-0.11.1.tar.gz", hash = "sha256:2798de800fa92780e33acca925945e9a19a133b715067cf165b8866c15a31687"}, +] + +[[package]] +name = "platformdirs" +version = "3.8.0" +description = "A small Python package for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." +optional = false +python-versions = ">=3.7" +files = [ + {file = "platformdirs-3.8.0-py3-none-any.whl", hash = "sha256:ca9ed98ce73076ba72e092b23d3c93ea6c4e186b3f1c3dad6edd98ff6ffcca2e"}, + {file = "platformdirs-3.8.0.tar.gz", hash = "sha256:b0cabcb11063d21a0b261d557acb0a9d2126350e63b70cdf7db6347baea456dc"}, +] + +[package.extras] +docs = ["furo (>=2023.5.20)", "proselint (>=0.13)", "sphinx (>=7.0.1)", "sphinx-autodoc-typehints (>=1.23,!=1.23.4)"] +test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=7.3.1)", "pytest-cov (>=4.1)", "pytest-mock (>=3.10)"] + +[[package]] +name = "pluggy" +version = "1.2.0" +description = "plugin and hook calling mechanisms for python" +optional = false +python-versions = ">=3.7" +files = [ + {file = "pluggy-1.2.0-py3-none-any.whl", hash = "sha256:c2fd55a7d7a3863cba1a013e4e2414658b1d07b6bc57b3919e0c63c9abb99849"}, + {file = "pluggy-1.2.0.tar.gz", hash = "sha256:d12f0c4b579b15f5e054301bb226ee85eeeba08ffec228092f8defbaa3a4c4b3"}, +] + +[package.extras] +dev = ["pre-commit", "tox"] +testing = ["pytest", "pytest-benchmark"] + +[[package]] +name = "pre-commit" +version = "2.21.0" +description = "A framework for managing and maintaining multi-language pre-commit hooks." +optional = false +python-versions = ">=3.7" +files = [ + {file = "pre_commit-2.21.0-py2.py3-none-any.whl", hash = "sha256:e2f91727039fc39a92f58a588a25b87f936de6567eed4f0e673e0507edc75bad"}, + {file = "pre_commit-2.21.0.tar.gz", hash = "sha256:31ef31af7e474a8d8995027fefdfcf509b5c913ff31f2015b4ec4beb26a6f658"}, +] + +[package.dependencies] +cfgv = ">=2.0.0" +identify = ">=1.0.0" +nodeenv = ">=0.11.1" +pyyaml = ">=5.1" +virtualenv = ">=20.10.0" + +[[package]] +name = "pydantic" +version = "1.10.9" +description = "Data validation and settings management using python type hints" +optional = false +python-versions = ">=3.7" +files = [ + {file = "pydantic-1.10.9-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e692dec4a40bfb40ca530e07805b1208c1de071a18d26af4a2a0d79015b352ca"}, + {file = "pydantic-1.10.9-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:3c52eb595db83e189419bf337b59154bdcca642ee4b2a09e5d7797e41ace783f"}, + {file = "pydantic-1.10.9-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:939328fd539b8d0edf244327398a667b6b140afd3bf7e347cf9813c736211896"}, + {file = "pydantic-1.10.9-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b48d3d634bca23b172f47f2335c617d3fcb4b3ba18481c96b7943a4c634f5c8d"}, + {file = "pydantic-1.10.9-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:f0b7628fb8efe60fe66fd4adadd7ad2304014770cdc1f4934db41fe46cc8825f"}, + {file = "pydantic-1.10.9-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:e1aa5c2410769ca28aa9a7841b80d9d9a1c5f223928ca8bec7e7c9a34d26b1d4"}, + {file = "pydantic-1.10.9-cp310-cp310-win_amd64.whl", hash = "sha256:eec39224b2b2e861259d6f3c8b6290d4e0fbdce147adb797484a42278a1a486f"}, + {file = "pydantic-1.10.9-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:d111a21bbbfd85c17248130deac02bbd9b5e20b303338e0dbe0faa78330e37e0"}, + {file = "pydantic-1.10.9-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:2e9aec8627a1a6823fc62fb96480abe3eb10168fd0d859ee3d3b395105ae19a7"}, + {file = "pydantic-1.10.9-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:07293ab08e7b4d3c9d7de4949a0ea571f11e4557d19ea24dd3ae0c524c0c334d"}, + {file = "pydantic-1.10.9-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7ee829b86ce984261d99ff2fd6e88f2230068d96c2a582f29583ed602ef3fc2c"}, + {file = "pydantic-1.10.9-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:4b466a23009ff5cdd7076eb56aca537c745ca491293cc38e72bf1e0e00de5b91"}, + {file = "pydantic-1.10.9-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:7847ca62e581e6088d9000f3c497267868ca2fa89432714e21a4fb33a04d52e8"}, + {file = "pydantic-1.10.9-cp311-cp311-win_amd64.whl", hash = "sha256:7845b31959468bc5b78d7b95ec52fe5be32b55d0d09983a877cca6aedc51068f"}, + {file = "pydantic-1.10.9-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:517a681919bf880ce1dac7e5bc0c3af1e58ba118fd774da2ffcd93c5f96eaece"}, + {file = "pydantic-1.10.9-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:67195274fd27780f15c4c372f4ba9a5c02dad6d50647b917b6a92bf00b3d301a"}, + {file = "pydantic-1.10.9-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2196c06484da2b3fded1ab6dbe182bdabeb09f6318b7fdc412609ee2b564c49a"}, + {file = "pydantic-1.10.9-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:6257bb45ad78abacda13f15bde5886efd6bf549dd71085e64b8dcf9919c38b60"}, + {file = "pydantic-1.10.9-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:3283b574b01e8dbc982080d8287c968489d25329a463b29a90d4157de4f2baaf"}, + {file = "pydantic-1.10.9-cp37-cp37m-win_amd64.whl", hash = "sha256:5f8bbaf4013b9a50e8100333cc4e3fa2f81214033e05ac5aa44fa24a98670a29"}, + {file = "pydantic-1.10.9-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:b9cd67fb763248cbe38f0593cd8611bfe4b8ad82acb3bdf2b0898c23415a1f82"}, + {file = "pydantic-1.10.9-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:f50e1764ce9353be67267e7fd0da08349397c7db17a562ad036aa7c8f4adfdb6"}, + {file = "pydantic-1.10.9-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:73ef93e5e1d3c8e83f1ff2e7fdd026d9e063c7e089394869a6e2985696693766"}, + {file = "pydantic-1.10.9-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:128d9453d92e6e81e881dd7e2484e08d8b164da5507f62d06ceecf84bf2e21d3"}, + {file = "pydantic-1.10.9-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:ad428e92ab68798d9326bb3e5515bc927444a3d71a93b4a2ca02a8a5d795c572"}, + {file = "pydantic-1.10.9-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:fab81a92f42d6d525dd47ced310b0c3e10c416bbfae5d59523e63ea22f82b31e"}, + {file = "pydantic-1.10.9-cp38-cp38-win_amd64.whl", hash = "sha256:963671eda0b6ba6926d8fc759e3e10335e1dc1b71ff2a43ed2efd6996634dafb"}, + {file = "pydantic-1.10.9-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:970b1bdc6243ef663ba5c7e36ac9ab1f2bfecb8ad297c9824b542d41a750b298"}, + {file = "pydantic-1.10.9-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:7e1d5290044f620f80cf1c969c542a5468f3656de47b41aa78100c5baa2b8276"}, + {file = "pydantic-1.10.9-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:83fcff3c7df7adff880622a98022626f4f6dbce6639a88a15a3ce0f96466cb60"}, + {file = "pydantic-1.10.9-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0da48717dc9495d3a8f215e0d012599db6b8092db02acac5e0d58a65248ec5bc"}, + {file = "pydantic-1.10.9-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:0a2aabdc73c2a5960e87c3ffebca6ccde88665616d1fd6d3db3178ef427b267a"}, + {file = "pydantic-1.10.9-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:9863b9420d99dfa9c064042304868e8ba08e89081428a1c471858aa2af6f57c4"}, + {file = "pydantic-1.10.9-cp39-cp39-win_amd64.whl", hash = "sha256:e7c9900b43ac14110efa977be3da28931ffc74c27e96ee89fbcaaf0b0fe338e1"}, + {file = "pydantic-1.10.9-py3-none-any.whl", hash = "sha256:6cafde02f6699ce4ff643417d1a9223716ec25e228ddc3b436fe7e2d25a1f305"}, + {file = "pydantic-1.10.9.tar.gz", hash = "sha256:95c70da2cd3b6ddf3b9645ecaa8d98f3d80c606624b6d245558d202cd23ea3be"}, +] + +[package.dependencies] +typing-extensions = ">=4.2.0" + +[package.extras] +dotenv = ["python-dotenv (>=0.10.4)"] +email = ["email-validator (>=1.0.3)"] + +[[package]] +name = "pygments" +version = "2.15.1" +description = "Pygments is a syntax highlighting package written in Python." +optional = false +python-versions = ">=3.7" +files = [ + {file = "Pygments-2.15.1-py3-none-any.whl", hash = "sha256:db2db3deb4b4179f399a09054b023b6a586b76499d36965813c71aa8ed7b5fd1"}, + {file = "Pygments-2.15.1.tar.gz", hash = "sha256:8ace4d3c1dd481894b2005f560ead0f9f19ee64fe983366be1a21e171d12775c"}, +] + +[package.extras] +plugins = ["importlib-metadata"] + +[[package]] +name = "pyjwt" +version = "2.8.0" +description = "JSON Web Token implementation in Python" +optional = false +python-versions = ">=3.7" +files = [ + {file = "PyJWT-2.8.0-py3-none-any.whl", hash = "sha256:59127c392cc44c2da5bb3192169a91f429924e17aff6534d70fdc02ab3e04320"}, + {file = "PyJWT-2.8.0.tar.gz", hash = "sha256:57e28d156e3d5c10088e0c68abb90bfac3df82b40a71bd0daa20c65ccd5c23de"}, +] + +[package.extras] +crypto = ["cryptography (>=3.4.0)"] +dev = ["coverage[toml] (==5.0.4)", "cryptography (>=3.4.0)", "pre-commit", "pytest (>=6.0.0,<7.0.0)", "sphinx (>=4.5.0,<5.0.0)", "sphinx-rtd-theme", "zope.interface"] +docs = ["sphinx (>=4.5.0,<5.0.0)", "sphinx-rtd-theme", "zope.interface"] +tests = ["coverage[toml] (==5.0.4)", "pytest (>=6.0.0,<7.0.0)"] + +[[package]] +name = "pyparsing" +version = "3.1.0" +description = "pyparsing module - Classes and methods to define and execute parsing grammars" +optional = false +python-versions = ">=3.6.8" +files = [ + {file = "pyparsing-3.1.0-py3-none-any.whl", hash = "sha256:d554a96d1a7d3ddaf7183104485bc19fd80543ad6ac5bdb6426719d766fb06c1"}, + {file = "pyparsing-3.1.0.tar.gz", hash = "sha256:edb662d6fe322d6e990b1594b5feaeadf806803359e3d4d42f11e295e588f0ea"}, +] + +[package.extras] +diagrams = ["jinja2", "railroad-diagrams"] + +[[package]] +name = "pytest" +version = "7.3.2" +description = "pytest: simple powerful testing with Python" +optional = false +python-versions = ">=3.7" +files = [ + {file = "pytest-7.3.2-py3-none-any.whl", hash = "sha256:cdcbd012c9312258922f8cd3f1b62a6580fdced17db6014896053d47cddf9295"}, + {file = "pytest-7.3.2.tar.gz", hash = "sha256:ee990a3cc55ba808b80795a79944756f315c67c12b56abd3ac993a7b8c17030b"}, +] + +[package.dependencies] +colorama = {version = "*", markers = "sys_platform == \"win32\""} +exceptiongroup = {version = ">=1.0.0rc8", markers = "python_version < \"3.11\""} +iniconfig = "*" +packaging = "*" +pluggy = ">=0.12,<2.0" +tomli = {version = ">=1.0.0", markers = "python_version < \"3.11\""} + +[package.extras] +testing = ["argcomplete", "attrs (>=19.2.0)", "hypothesis (>=3.56)", "mock", "nose", "pygments (>=2.7.2)", "requests", "setuptools", "xmlschema"] + +[[package]] +name = "pyyaml" +version = "6.0" +description = "YAML parser and emitter for Python" +optional = false +python-versions = ">=3.6" +files = [ + {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-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:d4b0ba9512519522b118090257be113b9468d804b19d63c71dbcf4a48fa32358"}, + {file = "PyYAML-6.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:81957921f441d50af23654aa6c5e5eaf9b06aba7f0a19c18a538dc7ef291c5a1"}, + {file = "PyYAML-6.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:afa17f5bc4d1b10afd4466fd3a44dc0e245382deca5b3c353d8b757f9e3ecb8d"}, + {file = "PyYAML-6.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:dbad0e9d368bb989f4515da330b88a057617d16b6a8245084f1b05400f24609f"}, + {file = "PyYAML-6.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:432557aa2c09802be39460360ddffd48156e30721f5e8d917f01d31694216782"}, + {file = "PyYAML-6.0-cp311-cp311-win32.whl", hash = "sha256:bfaef573a63ba8923503d27530362590ff4f576c626d86a9fed95822a8255fd7"}, + {file = "PyYAML-6.0-cp311-cp311-win_amd64.whl", hash = "sha256:01b45c0191e6d66c470b6cf1b9531a771a83c1c4208272ead47a3ae4f2f603bf"}, + {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"}, +] + +[[package]] +name = "requests" +version = "2.31.0" +description = "Python HTTP for Humans." +optional = false +python-versions = ">=3.7" +files = [ + {file = "requests-2.31.0-py3-none-any.whl", hash = "sha256:58cd2187c01e70e6e26505bca751777aa9f2ee0b7f4300988b709f44e013003f"}, + {file = "requests-2.31.0.tar.gz", hash = "sha256:942c5a758f98d790eaed1a29cb6eefc7ffb0d1cf7af05c3d2791656dbd6ad1e1"}, +] + +[package.dependencies] +certifi = ">=2017.4.17" +charset-normalizer = ">=2,<4" +idna = ">=2.5,<4" +urllib3 = ">=1.21.1,<3" + +[package.extras] +socks = ["PySocks (>=1.5.6,!=1.5.7)"] +use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"] + +[[package]] +name = "requests-oauthlib" +version = "1.3.1" +description = "OAuthlib authentication support for Requests." +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +files = [ + {file = "requests-oauthlib-1.3.1.tar.gz", hash = "sha256:75beac4a47881eeb94d5ea5d6ad31ef88856affe2332b9aafb52c6452ccf0d7a"}, + {file = "requests_oauthlib-1.3.1-py2.py3-none-any.whl", hash = "sha256:2577c501a2fb8d05a304c09d090d6e47c306fef15809d102b327cf8364bddab5"}, +] + +[package.dependencies] +oauthlib = ">=3.0.0" +requests = ">=2.0.0" + +[package.extras] +rsa = ["oauthlib[signedtoken] (>=3.0.0)"] + +[[package]] +name = "rich" +version = "13.4.2" +description = "Render rich text, tables, progress bars, syntax highlighting, markdown and more to the terminal" +optional = false +python-versions = ">=3.7.0" +files = [ + {file = "rich-13.4.2-py3-none-any.whl", hash = "sha256:8f87bc7ee54675732fa66a05ebfe489e27264caeeff3728c945d25971b6485ec"}, + {file = "rich-13.4.2.tar.gz", hash = "sha256:d653d6bccede5844304c605d5aac802c7cf9621efd700b46c7ec2b51ea914898"}, +] + +[package.dependencies] +markdown-it-py = ">=2.2.0" +pygments = ">=2.13.0,<3.0.0" +typing-extensions = {version = ">=4.0.0,<5.0", markers = "python_version < \"3.9\""} + +[package.extras] +jupyter = ["ipywidgets (>=7.5.1,<9)"] + +[[package]] +name = "ruamel-yaml" +version = "0.17.32" +description = "ruamel.yaml is a YAML parser/emitter that supports roundtrip preservation of comments, seq/map flow style, and map key order" +optional = false +python-versions = ">=3" +files = [ + {file = "ruamel.yaml-0.17.32-py3-none-any.whl", hash = "sha256:23cd2ed620231677564646b0c6a89d138b6822a0d78656df7abda5879ec4f447"}, + {file = "ruamel.yaml-0.17.32.tar.gz", hash = "sha256:ec939063761914e14542972a5cba6d33c23b0859ab6342f61cf070cfc600efc2"}, +] + +[package.dependencies] +"ruamel.yaml.clib" = {version = ">=0.2.7", markers = "platform_python_implementation == \"CPython\" and python_version < \"3.12\""} + +[package.extras] +docs = ["ryd"] +jinja2 = ["ruamel.yaml.jinja2 (>=0.2)"] + +[[package]] +name = "ruamel-yaml-clib" +version = "0.2.7" +description = "C version of reader, parser and emitter for ruamel.yaml derived from libyaml" +optional = false +python-versions = ">=3.5" +files = [ + {file = "ruamel.yaml.clib-0.2.7-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:d5859983f26d8cd7bb5c287ef452e8aacc86501487634573d260968f753e1d71"}, + {file = "ruamel.yaml.clib-0.2.7-cp310-cp310-macosx_12_0_arm64.whl", hash = "sha256:debc87a9516b237d0466a711b18b6ebeb17ba9f391eb7f91c649c5c4ec5006c7"}, + {file = "ruamel.yaml.clib-0.2.7-cp310-cp310-manylinux2014_aarch64.whl", hash = "sha256:df5828871e6648db72d1c19b4bd24819b80a755c4541d3409f0f7acd0f335c80"}, + {file = "ruamel.yaml.clib-0.2.7-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:efa08d63ef03d079dcae1dfe334f6c8847ba8b645d08df286358b1f5293d24ab"}, + {file = "ruamel.yaml.clib-0.2.7-cp310-cp310-win32.whl", hash = "sha256:763d65baa3b952479c4e972669f679fe490eee058d5aa85da483ebae2009d231"}, + {file = "ruamel.yaml.clib-0.2.7-cp310-cp310-win_amd64.whl", hash = "sha256:d000f258cf42fec2b1bbf2863c61d7b8918d31ffee905da62dede869254d3b8a"}, + {file = "ruamel.yaml.clib-0.2.7-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:045e0626baf1c52e5527bd5db361bc83180faaba2ff586e763d3d5982a876a9e"}, + {file = "ruamel.yaml.clib-0.2.7-cp311-cp311-macosx_12_6_arm64.whl", hash = "sha256:721bc4ba4525f53f6a611ec0967bdcee61b31df5a56801281027a3a6d1c2daf5"}, + {file = "ruamel.yaml.clib-0.2.7-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:41d0f1fa4c6830176eef5b276af04c89320ea616655d01327d5ce65e50575c94"}, + {file = "ruamel.yaml.clib-0.2.7-cp311-cp311-win32.whl", hash = "sha256:f6d3d39611ac2e4f62c3128a9eed45f19a6608670c5a2f4f07f24e8de3441d38"}, + {file = "ruamel.yaml.clib-0.2.7-cp311-cp311-win_amd64.whl", hash = "sha256:da538167284de58a52109a9b89b8f6a53ff8437dd6dc26d33b57bf6699153122"}, + {file = "ruamel.yaml.clib-0.2.7-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:4b3a93bb9bc662fc1f99c5c3ea8e623d8b23ad22f861eb6fce9377ac07ad6072"}, + {file = "ruamel.yaml.clib-0.2.7-cp36-cp36m-macosx_12_0_arm64.whl", hash = "sha256:a234a20ae07e8469da311e182e70ef6b199d0fbeb6c6cc2901204dd87fb867e8"}, + {file = "ruamel.yaml.clib-0.2.7-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:15910ef4f3e537eea7fe45f8a5d19997479940d9196f357152a09031c5be59f3"}, + {file = "ruamel.yaml.clib-0.2.7-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:370445fd795706fd291ab00c9df38a0caed0f17a6fb46b0f607668ecb16ce763"}, + {file = "ruamel.yaml.clib-0.2.7-cp36-cp36m-win32.whl", hash = "sha256:ecdf1a604009bd35c674b9225a8fa609e0282d9b896c03dd441a91e5f53b534e"}, + {file = "ruamel.yaml.clib-0.2.7-cp36-cp36m-win_amd64.whl", hash = "sha256:f34019dced51047d6f70cb9383b2ae2853b7fc4dce65129a5acd49f4f9256646"}, + {file = "ruamel.yaml.clib-0.2.7-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:2aa261c29a5545adfef9296b7e33941f46aa5bbd21164228e833412af4c9c75f"}, + {file = "ruamel.yaml.clib-0.2.7-cp37-cp37m-macosx_12_0_arm64.whl", hash = "sha256:f01da5790e95815eb5a8a138508c01c758e5f5bc0ce4286c4f7028b8dd7ac3d0"}, + {file = "ruamel.yaml.clib-0.2.7-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:40d030e2329ce5286d6b231b8726959ebbe0404c92f0a578c0e2482182e38282"}, + {file = "ruamel.yaml.clib-0.2.7-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:c3ca1fbba4ae962521e5eb66d72998b51f0f4d0f608d3c0347a48e1af262efa7"}, + {file = "ruamel.yaml.clib-0.2.7-cp37-cp37m-win32.whl", hash = "sha256:7bdb4c06b063f6fd55e472e201317a3bb6cdeeee5d5a38512ea5c01e1acbdd93"}, + {file = "ruamel.yaml.clib-0.2.7-cp37-cp37m-win_amd64.whl", hash = "sha256:be2a7ad8fd8f7442b24323d24ba0b56c51219513cfa45b9ada3b87b76c374d4b"}, + {file = "ruamel.yaml.clib-0.2.7-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:91a789b4aa0097b78c93e3dc4b40040ba55bef518f84a40d4442f713b4094acb"}, + {file = "ruamel.yaml.clib-0.2.7-cp38-cp38-macosx_12_0_arm64.whl", hash = "sha256:99e77daab5d13a48a4054803d052ff40780278240a902b880dd37a51ba01a307"}, + {file = "ruamel.yaml.clib-0.2.7-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:3243f48ecd450eddadc2d11b5feb08aca941b5cd98c9b1db14b2fd128be8c697"}, + {file = "ruamel.yaml.clib-0.2.7-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:8831a2cedcd0f0927f788c5bdf6567d9dc9cc235646a434986a852af1cb54b4b"}, + {file = "ruamel.yaml.clib-0.2.7-cp38-cp38-win32.whl", hash = "sha256:3110a99e0f94a4a3470ff67fc20d3f96c25b13d24c6980ff841e82bafe827cac"}, + {file = "ruamel.yaml.clib-0.2.7-cp38-cp38-win_amd64.whl", hash = "sha256:92460ce908546ab69770b2e576e4f99fbb4ce6ab4b245345a3869a0a0410488f"}, + {file = "ruamel.yaml.clib-0.2.7-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:5bc0667c1eb8f83a3752b71b9c4ba55ef7c7058ae57022dd9b29065186a113d9"}, + {file = "ruamel.yaml.clib-0.2.7-cp39-cp39-macosx_12_0_arm64.whl", hash = "sha256:4a4d8d417868d68b979076a9be6a38c676eca060785abaa6709c7b31593c35d1"}, + {file = "ruamel.yaml.clib-0.2.7-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:bf9a6bc4a0221538b1a7de3ed7bca4c93c02346853f44e1cd764be0023cd3640"}, + {file = "ruamel.yaml.clib-0.2.7-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:a7b301ff08055d73223058b5c46c55638917f04d21577c95e00e0c4d79201a6b"}, + {file = "ruamel.yaml.clib-0.2.7-cp39-cp39-win32.whl", hash = "sha256:d5e51e2901ec2366b79f16c2299a03e74ba4531ddcfacc1416639c557aef0ad8"}, + {file = "ruamel.yaml.clib-0.2.7-cp39-cp39-win_amd64.whl", hash = "sha256:184faeaec61dbaa3cace407cffc5819f7b977e75360e8d5ca19461cd851a5fc5"}, + {file = "ruamel.yaml.clib-0.2.7.tar.gz", hash = "sha256:1f08fd5a2bea9c4180db71678e850b995d2a5f4537be0e94557668cf0f5f9497"}, +] + +[[package]] +name = "ruff" +version = "0.0.263" +description = "An extremely fast Python linter, written in Rust." +optional = false +python-versions = ">=3.7" +files = [ + {file = "ruff-0.0.263-py3-none-macosx_10_7_x86_64.whl", hash = "sha256:ee6c7a77f142c427fa73e1f5f603fc1a39413a36fe6966ed0fc55e97f6921d9c"}, + {file = "ruff-0.0.263-py3-none-macosx_10_9_x86_64.macosx_11_0_arm64.macosx_10_9_universal2.whl", hash = "sha256:c3b7d4b365207f3e4c40d235127091478e595b31e35b6cd57d940920cdfae68b"}, + {file = "ruff-0.0.263-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ebc778d95f29c9917e6e7608b2b67815707e6ab8eb5af9341617beda479c3edf"}, + {file = "ruff-0.0.263-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:4f75fa1632ea065b8f10678e7b6ae9873f84d5046bdf146990112751e98af42a"}, + {file = "ruff-0.0.263-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ddcee0d91629a4fa4bc9faebf5b94d4615d50d1cd76d1098fa71fbe1c54f4104"}, + {file = "ruff-0.0.263-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:4010b156f2e9fa6e74b5581098467f6ff68beac48945599b3a9239481e578ab4"}, + {file = "ruff-0.0.263-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:15386933dd8e03aafa3186f9e996d6823105492817311338fbcb64d0ecbcd95f"}, + {file = "ruff-0.0.263-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:04e0b280dd246448564c892bce5607d820ad1f14944f3d535db98692e2a7ac07"}, + {file = "ruff-0.0.263-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:82c41f276106017b6f075dd2f2cc68e1a0b434cc75488f816fc98bd41982628d"}, + {file = "ruff-0.0.263-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:3e9fcee3f81129eabc75da005d839235e32d7d374f2d4c0db0c708dad4703d6e"}, + {file = "ruff-0.0.263-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:981e3c4d773f7ff52479c4fd74a65e408f1e13fa5f889b72214d400cd1299ce4"}, + {file = "ruff-0.0.263-py3-none-musllinux_1_2_i686.whl", hash = "sha256:bed1d3fba306e3f7e13ce226927b84200350e25abd1e754e06ee361c6d41de15"}, + {file = "ruff-0.0.263-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:7890499c2c3dcb1e60de2a8b4c5f5775b2bfcdff7d3e68e38db5cb2d65b12006"}, + {file = "ruff-0.0.263-py3-none-win32.whl", hash = "sha256:c2b79919ebd93674b93dfc2c843e264bf8e52fbe737467e9b58521775c85f4ad"}, + {file = "ruff-0.0.263-py3-none-win_amd64.whl", hash = "sha256:9af932f665e177de62e172901704257fd6e5bfabb95893867ff7382a851459d3"}, + {file = "ruff-0.0.263-py3-none-win_arm64.whl", hash = "sha256:ddf4503595b560bfa5fae92fa2e4cb09ec465ee4cf88cc248f10ad2e956deec3"}, + {file = "ruff-0.0.263.tar.gz", hash = "sha256:1008f211ad8aa1d998517ac5bf3d68fbc68ec516d1da89b6081f25ff2f30b687"}, +] + +[[package]] +name = "safety" +version = "2.3.5" +description = "Checks installed dependencies for known vulnerabilities and licenses." +optional = false +python-versions = "*" +files = [ + {file = "safety-2.3.5-py3-none-any.whl", hash = "sha256:2227fcac1b22b53c1615af78872b48348661691450aa25d6704a5504dbd1f7e2"}, + {file = "safety-2.3.5.tar.gz", hash = "sha256:a60c11f8952f412cbb165d70cb1f673a3b43a2ba9a93ce11f97e6a4de834aa3a"}, +] + +[package.dependencies] +Click = ">=8.0.2" +dparse = ">=0.6.2" +packaging = ">=21.0,<22.0" +requests = "*" +"ruamel.yaml" = ">=0.17.21" +setuptools = ">=19.3" + +[package.extras] +github = ["jinja2 (>=3.1.0)", "pygithub (>=1.43.3)"] +gitlab = ["python-gitlab (>=1.3.0)"] + +[[package]] +name = "setuptools" +version = "68.0.0" +description = "Easily download, build, install, upgrade, and uninstall Python packages" +optional = false +python-versions = ">=3.7" +files = [ + {file = "setuptools-68.0.0-py3-none-any.whl", hash = "sha256:11e52c67415a381d10d6b462ced9cfb97066179f0e871399e006c4ab101fc85f"}, + {file = "setuptools-68.0.0.tar.gz", hash = "sha256:baf1fdb41c6da4cd2eae722e135500da913332ab3f2f5c7d33af9b492acb5235"}, +] + +[package.extras] +docs = ["furo", "jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-favicon", "sphinx-hoverxref (<2)", "sphinx-inline-tabs", "sphinx-lint", "sphinx-notfound-page (==0.8.3)", "sphinx-reredirects", "sphinxcontrib-towncrier"] +testing = ["build[virtualenv]", "filelock (>=3.4.0)", "flake8-2020", "ini2toml[lite] (>=0.9)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "pip (>=19.1)", "pip-run (>=8.8)", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-mypy (>=0.9.1)", "pytest-perf", "pytest-ruff", "pytest-timeout", "pytest-xdist", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel"] +testing-integration = ["build[virtualenv]", "filelock (>=3.4.0)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "pytest", "pytest-enabler", "pytest-xdist", "tomli", "virtualenv (>=13.0.0)", "wheel"] + +[[package]] +name = "six" +version = "1.16.0" +description = "Python 2 and 3 compatibility utilities" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" +files = [ + {file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"}, + {file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"}, +] + +[[package]] +name = "toml" +version = "0.10.2" +description = "Python Library for Tom's Obvious, Minimal Language" +optional = false +python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" +files = [ + {file = "toml-0.10.2-py2.py3-none-any.whl", hash = "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b"}, + {file = "toml-0.10.2.tar.gz", hash = "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f"}, +] + +[[package]] +name = "tomli" +version = "2.0.1" +description = "A lil' TOML parser" +optional = false +python-versions = ">=3.7" +files = [ + {file = "tomli-2.0.1-py3-none-any.whl", hash = "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc"}, + {file = "tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"}, +] + +[[package]] +name = "types-python-dateutil" +version = "2.8.19.13" +description = "Typing stubs for python-dateutil" +optional = false +python-versions = "*" +files = [ + {file = "types-python-dateutil-2.8.19.13.tar.gz", hash = "sha256:09a0275f95ee31ce68196710ed2c3d1b9dc42e0b61cc43acc369a42cb939134f"}, + {file = "types_python_dateutil-2.8.19.13-py3-none-any.whl", hash = "sha256:0b0e7c68e7043b0354b26a1e0225cb1baea7abb1b324d02b50e2d08f1221043f"}, +] + +[[package]] +name = "types-requests" +version = "2.31.0.1" +description = "Typing stubs for requests" +optional = false +python-versions = "*" +files = [ + {file = "types-requests-2.31.0.1.tar.gz", hash = "sha256:3de667cffa123ce698591de0ad7db034a5317457a596eb0b4944e5a9d9e8d1ac"}, + {file = "types_requests-2.31.0.1-py3-none-any.whl", hash = "sha256:afb06ef8f25ba83d59a1d424bd7a5a939082f94b94e90ab5e6116bd2559deaa3"}, +] + +[package.dependencies] +types-urllib3 = "*" + +[[package]] +name = "types-urllib3" +version = "1.26.25.13" +description = "Typing stubs for urllib3" +optional = false +python-versions = "*" +files = [ + {file = "types-urllib3-1.26.25.13.tar.gz", hash = "sha256:3300538c9dc11dad32eae4827ac313f5d986b8b21494801f1bf97a1ac6c03ae5"}, + {file = "types_urllib3-1.26.25.13-py3-none-any.whl", hash = "sha256:5dbd1d2bef14efee43f5318b5d36d805a489f6600252bb53626d4bfafd95e27c"}, +] + +[[package]] +name = "typing-extensions" +version = "4.6.3" +description = "Backported and Experimental Type Hints for Python 3.7+" +optional = false +python-versions = ">=3.7" +files = [ + {file = "typing_extensions-4.6.3-py3-none-any.whl", hash = "sha256:88a4153d8505aabbb4e13aacb7c486c2b4a33ca3b3f807914a9b4c844c471c26"}, + {file = "typing_extensions-4.6.3.tar.gz", hash = "sha256:d91d5919357fe7f681a9f2b5b4cb2a5f1ef0a1e9f59c4d8ff0d3491e05c0ffd5"}, +] + +[[package]] +name = "ujson" +version = "5.7.0" +description = "Ultra fast JSON encoder and decoder for Python" +optional = false +python-versions = ">=3.7" +files = [ + {file = "ujson-5.7.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:5eba5e69e4361ac3a311cf44fa71bc619361b6e0626768a494771aacd1c2f09b"}, + {file = "ujson-5.7.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:aae4d9e1b4c7b61780f0a006c897a4a1904f862fdab1abb3ea8f45bd11aa58f3"}, + {file = "ujson-5.7.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d2e43ccdba1cb5c6d3448eadf6fc0dae7be6c77e357a3abc968d1b44e265866d"}, + {file = "ujson-5.7.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:54384ce4920a6d35fa9ea8e580bc6d359e3eb961fa7e43f46c78e3ed162d56ff"}, + {file = "ujson-5.7.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:24ad1aa7fc4e4caa41d3d343512ce68e41411fb92adf7f434a4d4b3749dc8f58"}, + {file = "ujson-5.7.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:afff311e9f065a8f03c3753db7011bae7beb73a66189c7ea5fcb0456b7041ea4"}, + {file = "ujson-5.7.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:6e80f0d03e7e8646fc3d79ed2d875cebd4c83846e129737fdc4c2532dbd43d9e"}, + {file = "ujson-5.7.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:137831d8a0db302fb6828ee21c67ad63ac537bddc4376e1aab1c8573756ee21c"}, + {file = "ujson-5.7.0-cp310-cp310-win32.whl", hash = "sha256:7df3fd35ebc14dafeea031038a99232b32f53fa4c3ecddb8bed132a43eefb8ad"}, + {file = "ujson-5.7.0-cp310-cp310-win_amd64.whl", hash = "sha256:af4639f684f425177d09ae409c07602c4096a6287027469157bfb6f83e01448b"}, + {file = "ujson-5.7.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:9b0f2680ce8a70f77f5d70aaf3f013d53e6af6d7058727a35d8ceb4a71cdd4e9"}, + {file = "ujson-5.7.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:67a19fd8e7d8cc58a169bea99fed5666023adf707a536d8f7b0a3c51dd498abf"}, + {file = "ujson-5.7.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6abb8e6d8f1ae72f0ed18287245f5b6d40094e2656d1eab6d99d666361514074"}, + {file = "ujson-5.7.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d8cd622c069368d5074bd93817b31bdb02f8d818e57c29e206f10a1f9c6337dd"}, + {file = "ujson-5.7.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:14f9082669f90e18e64792b3fd0bf19f2b15e7fe467534a35ea4b53f3bf4b755"}, + {file = "ujson-5.7.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:d7ff6ebb43bc81b057724e89550b13c9a30eda0f29c2f506f8b009895438f5a6"}, + {file = "ujson-5.7.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:f7f241488879d91a136b299e0c4ce091996c684a53775e63bb442d1a8e9ae22a"}, + {file = "ujson-5.7.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:5593263a7fcfb934107444bcfba9dde8145b282de0ee9f61e285e59a916dda0f"}, + {file = "ujson-5.7.0-cp311-cp311-win32.whl", hash = "sha256:26c2b32b489c393106e9cb68d0a02e1a7b9d05a07429d875c46b94ee8405bdb7"}, + {file = "ujson-5.7.0-cp311-cp311-win_amd64.whl", hash = "sha256:ed24406454bb5a31df18f0a423ae14beb27b28cdfa34f6268e7ebddf23da807e"}, + {file = "ujson-5.7.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:18679484e3bf9926342b1c43a3bd640f93a9eeeba19ef3d21993af7b0c44785d"}, + {file = "ujson-5.7.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0ee295761e1c6c30400641f0a20d381633d7622633cdf83a194f3c876a0e4b7e"}, + {file = "ujson-5.7.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b738282e12a05f400b291966630a98d622da0938caa4bc93cf65adb5f4281c60"}, + {file = "ujson-5.7.0-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:00343501dbaa5172e78ef0e37f9ebd08040110e11c12420ff7c1f9f0332d939e"}, + {file = "ujson-5.7.0-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:c0d1f7c3908357ee100aa64c4d1cf91edf99c40ac0069422a4fd5fd23b263263"}, + {file = "ujson-5.7.0-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:a5d2f44331cf04689eafac7a6596c71d6657967c07ac700b0ae1c921178645da"}, + {file = "ujson-5.7.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:16b2254a77b310f118717715259a196662baa6b1f63b1a642d12ab1ff998c3d7"}, + {file = "ujson-5.7.0-cp37-cp37m-win32.whl", hash = "sha256:6faf46fa100b2b89e4db47206cf8a1ffb41542cdd34dde615b2fc2288954f194"}, + {file = "ujson-5.7.0-cp37-cp37m-win_amd64.whl", hash = "sha256:ff0004c3f5a9a6574689a553d1b7819d1a496b4f005a7451f339dc2d9f4cf98c"}, + {file = "ujson-5.7.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:75204a1dd7ec6158c8db85a2f14a68d2143503f4bafb9a00b63fe09d35762a5e"}, + {file = "ujson-5.7.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:7312731c7826e6c99cdd3ac503cd9acd300598e7a80bcf41f604fee5f49f566c"}, + {file = "ujson-5.7.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7b9dc5a90e2149643df7f23634fe202fed5ebc787a2a1be95cf23632b4d90651"}, + {file = "ujson-5.7.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b6a6961fc48821d84b1198a09516e396d56551e910d489692126e90bf4887d29"}, + {file = "ujson-5.7.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b01a9af52a0d5c46b2c68e3f258fdef2eacaa0ce6ae3e9eb97983f5b1166edb6"}, + {file = "ujson-5.7.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:b7316d3edeba8a403686cdcad4af737b8415493101e7462a70ff73dd0609eafc"}, + {file = "ujson-5.7.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:4ee997799a23227e2319a3f8817ce0b058923dbd31904761b788dc8f53bd3e30"}, + {file = "ujson-5.7.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:dda9aa4c33435147262cd2ea87c6b7a1ca83ba9b3933ff7df34e69fee9fced0c"}, + {file = "ujson-5.7.0-cp38-cp38-win32.whl", hash = "sha256:bea8d30e362180aafecabbdcbe0e1f0b32c9fa9e39c38e4af037b9d3ca36f50c"}, + {file = "ujson-5.7.0-cp38-cp38-win_amd64.whl", hash = "sha256:c96e3b872bf883090ddf32cc41957edf819c5336ab0007d0cf3854e61841726d"}, + {file = "ujson-5.7.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:6411aea4c94a8e93c2baac096fbf697af35ba2b2ed410b8b360b3c0957a952d3"}, + {file = "ujson-5.7.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:3d3b3499c55911f70d4e074c626acdb79a56f54262c3c83325ffb210fb03e44d"}, + {file = "ujson-5.7.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:341f891d45dd3814d31764626c55d7ab3fd21af61fbc99d070e9c10c1190680b"}, + {file = "ujson-5.7.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2f242eec917bafdc3f73a1021617db85f9958df80f267db69c76d766058f7b19"}, + {file = "ujson-5.7.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c3af9f9f22a67a8c9466a32115d9073c72a33ae627b11de6f592df0ee09b98b6"}, + {file = "ujson-5.7.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:4a3d794afbf134df3056a813e5c8a935208cddeae975bd4bc0ef7e89c52f0ce0"}, + {file = "ujson-5.7.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:800bf998e78dae655008dd10b22ca8dc93bdcfcc82f620d754a411592da4bbf2"}, + {file = "ujson-5.7.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:b5ac3d5c5825e30b438ea92845380e812a476d6c2a1872b76026f2e9d8060fc2"}, + {file = "ujson-5.7.0-cp39-cp39-win32.whl", hash = "sha256:cd90027e6d93e8982f7d0d23acf88c896d18deff1903dd96140613389b25c0dd"}, + {file = "ujson-5.7.0-cp39-cp39-win_amd64.whl", hash = "sha256:523ee146cdb2122bbd827f4dcc2a8e66607b3f665186bce9e4f78c9710b6d8ab"}, + {file = "ujson-5.7.0-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:e87cec407ec004cf1b04c0ed7219a68c12860123dfb8902ef880d3d87a71c172"}, + {file = "ujson-5.7.0-pp37-pypy37_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bab10165db6a7994e67001733f7f2caf3400b3e11538409d8756bc9b1c64f7e8"}, + {file = "ujson-5.7.0-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b522be14a28e6ac1cf818599aeff1004a28b42df4ed4d7bc819887b9dac915fc"}, + {file = "ujson-5.7.0-pp37-pypy37_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7592f40175c723c032cdbe9fe5165b3b5903604f774ab0849363386e99e1f253"}, + {file = "ujson-5.7.0-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:ed22f9665327a981f288a4f758a432824dc0314e4195a0eaeb0da56a477da94d"}, + {file = "ujson-5.7.0-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:adf445a49d9a97a5a4c9bb1d652a1528de09dd1c48b29f79f3d66cea9f826bf6"}, + {file = "ujson-5.7.0-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:64772a53f3c4b6122ed930ae145184ebaed38534c60f3d859d8c3f00911eb122"}, + {file = "ujson-5.7.0-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:35209cb2c13fcb9d76d249286105b4897b75a5e7f0efb0c0f4b90f222ce48910"}, + {file = "ujson-5.7.0-pp38-pypy38_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:90712dfc775b2c7a07d4d8e059dd58636bd6ff1776d79857776152e693bddea6"}, + {file = "ujson-5.7.0-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:0e4e8981c6e7e9e637e637ad8ffe948a09e5434bc5f52ecbb82b4b4cfc092bfb"}, + {file = "ujson-5.7.0-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:581c945b811a3d67c27566539bfcb9705ea09cb27c4be0002f7a553c8886b817"}, + {file = "ujson-5.7.0-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d36a807a24c7d44f71686685ae6fbc8793d784bca1adf4c89f5f780b835b6243"}, + {file = "ujson-5.7.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8b4257307e3662aa65e2644a277ca68783c5d51190ed9c49efebdd3cbfd5fa44"}, + {file = "ujson-5.7.0-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ea7423d8a2f9e160c5e011119741682414c5b8dce4ae56590a966316a07a4618"}, + {file = "ujson-5.7.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:4c592eb91a5968058a561d358d0fef59099ed152cfb3e1cd14eee51a7a93879e"}, + {file = "ujson-5.7.0.tar.gz", hash = "sha256:e788e5d5dcae8f6118ac9b45d0b891a0d55f7ac480eddcb7f07263f2bcf37b23"}, +] + +[[package]] +name = "urllib3" +version = "2.0.3" +description = "HTTP library with thread-safe connection pooling, file post, and more." +optional = false +python-versions = ">=3.7" +files = [ + {file = "urllib3-2.0.3-py3-none-any.whl", hash = "sha256:48e7fafa40319d358848e1bc6809b208340fafe2096f1725d05d67443d0483d1"}, + {file = "urllib3-2.0.3.tar.gz", hash = "sha256:bee28b5e56addb8226c96f7f13ac28cb4c301dd5ea8a6ca179c0b9835e032825"}, +] + +[package.extras] +brotli = ["brotli (>=1.0.9)", "brotlicffi (>=0.8.0)"] +secure = ["certifi", "cryptography (>=1.9)", "idna (>=2.0.0)", "pyopenssl (>=17.1.0)", "urllib3-secure-extra"] +socks = ["pysocks (>=1.5.6,!=1.5.7,<2.0)"] +zstd = ["zstandard (>=0.18.0)"] + +[[package]] +name = "virtualenv" +version = "20.23.1" +description = "Virtual Python Environment builder" +optional = false +python-versions = ">=3.7" +files = [ + {file = "virtualenv-20.23.1-py3-none-any.whl", hash = "sha256:34da10f14fea9be20e0fd7f04aba9732f84e593dac291b757ce42e3368a39419"}, + {file = "virtualenv-20.23.1.tar.gz", hash = "sha256:8ff19a38c1021c742148edc4f81cb43d7f8c6816d2ede2ab72af5b84c749ade1"}, +] + +[package.dependencies] +distlib = ">=0.3.6,<1" +filelock = ">=3.12,<4" +platformdirs = ">=3.5.1,<4" + +[package.extras] +docs = ["furo (>=2023.5.20)", "proselint (>=0.13)", "sphinx (>=7.0.1)", "sphinx-argparse (>=0.4)", "sphinxcontrib-towncrier (>=0.2.1a0)", "towncrier (>=23.6)"] +test = ["covdefaults (>=2.3)", "coverage (>=7.2.7)", "coverage-enable-subprocess (>=1)", "flaky (>=3.7)", "packaging (>=23.1)", "pytest (>=7.3.1)", "pytest-env (>=0.8.1)", "pytest-freezer (>=0.4.6)", "pytest-mock (>=3.10)", "pytest-randomly (>=3.12)", "pytest-timeout (>=2.1)", "setuptools (>=67.8)", "time-machine (>=2.9)"] + +[[package]] +name = "wikibaseintegrator" +version = "0.12.4" +description = "Python package for reading from and writing to a Wikibase instance" +optional = false +python-versions = "<3.13,>=3.7" +files = [ + {file = "wikibaseintegrator-0.12.4-py3-none-any.whl", hash = "sha256:789c0b31e3b4712b71821efba25c1e80a382e24a4e3116476afab615ca2c80c2"}, + {file = "wikibaseintegrator-0.12.4.tar.gz", hash = "sha256:ebcc7cd5300a0153b771184eb9020627f70d5d2060d033b505d5f7a4e87cdf71"}, +] + +[package.dependencies] +backoff = ">=1.11.1,<2.3.0" +mwoauth = ">=0.3.8,<0.4.0" +oauthlib = ">=3.2.0,<3.3.0" +requests = ">=2.27.1,<2.32.0" +requests-oauthlib = ">=1.3.1,<1.4.0" +ujson = ">=5.4,<5.8" + +[package.extras] +coverage = ["pytest-cov"] +dev = ["codespell", "flynt", "mypy", "pylint", "pylint-exit", "pytest"] +docs = ["Sphinx (>=4.5,<7.1)", "m2r2 (>=0.3.2,<0.4.0)", "readthedocs-sphinx-ext (>=2.1.5,<2.3.0)", "sphinx-autodoc-typehints (>=1.18.1,<1.24.0)", "sphinx-github-changelog (>=1.2.0,<1.3.0)", "sphinx-rtd-theme (>=1.0,<1.3)"] +notebooks = ["jupyter"] + +[metadata] +lock-version = "2.0" +python-versions = ">=3.8,<=3.12" +content-hash = "047e2982cc30bdfaefb533c38d4c181112a8e06c7693d1ff4a46633f7d4955b3" diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..46d043a --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,130 @@ +[tool.poetry] +name = "Entityshape" +version = "0.2.0" +description = "Python library to validate Wikidata items." +authors = ["Mark Tully aka Teester", "Dennis Priskorn <68460690+dpriskorn@users.noreply.github.com>"] +license = "GPLv3+" +readme = "README.md" +repository = "https://github.com/dpriskorn/entityshape" +keywords = ["wikidata", "entityschema", "entity validation"] +classifiers = [ + "Topic :: Software Development :: Libraries :: Python Modules" +] +packages = [ + { include = "entityshape" }, +] + +[tool.poetry.dependencies] +pydantic = "^1.10.9" +python = ">=3.8,<3.13" +requests = "^2.28.1" +wikibaseintegrator = "^0.12.4" + +[tool.poetry.group.dev.dependencies] +rich = "^13.4.2" +black = "^22.8.0" +codespell = "^2.2.1" +coverage = "^6.5.0" +dead = "^1.5.0" +mypy = "^1.1.1" +pre-commit = "^2.20.0" +pytest = "^7.1.3" +ruff = "^0.0.263" +safety = "^2.2.0" +tomli = "^2.0.1" +types-python-dateutil = "^2.8.19.2" +types-requests = "^2.28.11.2" + +[tool.ruff] +select = [ + "A", # flake8-builtins +# "ASYNC", # flake8-async + "ARG", # flake8-unused-arguments + "B", # flake8-bugbear + "BLE", # flake8-blind-except + "C4", # flake8-comprehensions + "C90", # McCabe cyclomatic complexity + "DTZ", # flake8-datetimez + "E", # pycodestyle + "EXE", # flake8-executable + "F", # Pyflakes + "FBT", # flake8-boolean-trap + "G", # flake8-logging-format + "I", # isort + "ICN", # flake8-import-conventions + "INP", # flake8-no-pep420 + "INT", # flake8-gettext + "ISC", # flake8-implicit-str-concat + "N", # pep8-naming + "PGH", # pygrep-hooks + "PIE", # flake8-pie + "PL", # Pylint + "PT", # flake8-pytest-style + "PTH", # flake8-use-pathlib + "PYI", # flake8-pyi + "RSE", # flake8-raise + "RUF", # Ruff-specific rules + "S", # flake8-bandit + "SIM", # flake8-simplify + "SLF", # flake8-self + "T10", # flake8-debugger + "TCH", # flake8-type-checking + "TID", # flake8-tidy-imports + "UP", # pyupgrade + "W", # pycodestyle + "YTT", # flake8-2020 + # "ANN", # flake8-annotations + # "COM", # flake8-commas + # "D", # pydocstyle + # "DJ", # flake8-django + # "EM", # flake8-errmsg + # "ERA", # eradicate + # "NPY", # NumPy-specific rules + # "PD", # pandas-vet + # "Q", # flake8-quotes + # "RET", # flake8-return + # "T20", # flake8-print + # "TRY", # tryceratops +] +ignore = [ + "A003", + "ARG002", + "DTZ003", + "EXE002", + "F401", + "FBT001", + "FBT002", + "G003", + "G004", + "INP001", + "N999", + "PGH003", + "PLC1901", + "PLR2004", + "PLR5501", + "PLW2901", + "PTH110", + "PTH123", + #"RUF012", # Error: src/models/api/handlers/all.py:18:35: RUF012 Mutable class attributes should be annotated with `typing.ClassVar` + "RSE102", + "S113", + "S324", + "S501", + "PLR0913", + ] +line-length = 160 +target-version = "py38" + +[tool.ruff.isort] +known-first-party = ["config"] + +[tool.ruff.mccabe] +max-complexity = 11 + +[tool.ruff.per-file-ignores] +"tests/*" = ["PT009", "PT018", "RUF001", "RUF003", "S101", "ISC001"] +"entityshape/models/compareshape.py" = ["SIM114"] + +[build-system] +requires = ["poetry-core"] +build-backend = "poetry.core.masonry.api" diff --git a/pytest.ini b/pytest.ini new file mode 100644 index 0000000..5ae9c38 --- /dev/null +++ b/pytest.ini @@ -0,0 +1,3 @@ +[pytest] +log_cli = 1 +log_cli_level = WARNING diff --git a/requirements.txt b/requirements.txt deleted file mode 100644 index 8f53a8d..0000000 --- a/requirements.txt +++ /dev/null @@ -1,3 +0,0 @@ -flask -flask_cors -requests diff --git a/script.js b/script.js deleted file mode 100644 index 5fdc74f..0000000 --- a/script.js +++ /dev/null @@ -1,237 +0,0 @@ -(function() { -let entitycheck_stylesheet = entitycheck_getStylesheet(); -$('html > head').append(""); - -$(document).ready(function(){ - let entityID = mw.config.get( 'wbEntityId' ); - let lang = mw.config.get( 'wgUserLanguage' ); - if (entityID) { - let schema = window.localStorage.getItem("entitycheck"); - let value = window.localStorage.getItem("entitycheck-auto"); - let entitycheck_entityName = document.location.pathname.substring(6); - if (value == "true") { - entitycheck_checkEntity(entitycheck_entityName, schema, lang); - $("#entitycheck-checkbox").prop('checked', true); - } else { - $("#entitycheck-checkbox").prop('checked', false); - } - $("#entityschema-entityToCheck:text").val(schema); - } -}); - -let entitycheck_conditions = ["/wiki/Q", "/wiki/P", "/wiki/L"]; -if (entitycheck_conditions.some(el => document.location.pathname.includes(el))) { - let entitycheck_entity_html = '
'; - entitycheck_entity_html += ''; - entitycheck_entity_html += ''; - entitycheck_entity_html += ''; - entitycheck_entity_html += '
'; - var entitycheck_entityTitle = $(".wikibase-title-id" )[0].innerText; - if (document.location.pathname.includes("/wiki/L")) { - $(".mw-indicators" ).append( entitycheck_entity_html ); - } else { - $(".wikibase-entitytermsview-heading" ).append( entitycheck_entity_html ); - } - $("#entityschema-schemaSearchButton").click(function(){ entitycheck_update() }); - $("#entitycheck-checkbox").click(function() { entitycheck_checkbox() }) -} - -$("#entityschema-entityToCheck").on("keyup", function(event){ - if (event.key === "Enter") { - event.preventDefault(); - $("#entityschema-schemaSearchButton").click(); - return false; - } -}); - -function entitycheck_update() { - let entitycheck_entitySchema = $("#entityschema-entityToCheck")[0].value.toUpperCase(); - let entitycheck_entityName = document.location.pathname.substring(6); - let lang = mw.config.get( 'wgUserLanguage' ); - window.localStorage.setItem("entitycheck", entitycheck_entitySchema); - entitycheck_checkEntity(entitycheck_entityName, entitycheck_entitySchema, lang); -} - -function entitycheck_checkbox() { - if ($('#entitycheck-checkbox').is(":checked")) { - window.localStorage.setItem("entitycheck-auto", true); - } else { - window.localStorage.setItem("entitycheck-auto", false); - } -} - -function entitycheck_checkEntity(entity, entitySchema, language) { - $("#entityCheckResponse").contents().remove(); - $(".entitycheck-property").remove(); - let url = "https://entityshape.toolforge.org/api?entityschema=" + entitySchema + "&entity=" + entity + "&language=" + language; - $.ajax({ - type: "GET", - dataType: "json", - url: url, - success: function(data){ - let html = ""; - - html += "
Checking against " + data.schema + ":" + data.name + ':'; - if (data.validity.results) { - if (data.validity.results[0].result) { - html += ' Pass
'; - } else { - html += entitycheck_parseResult(data.validity.results[0]); - } - } - html += '
'; - html += ''; - html += ''; - html += ''; - - let required_html = '"; - optional_html += ""; - absent_html += ""; - html += required_html + optional_html + absent_html; - html += '
Required propertiesOptional propertiesOther properties
'; - let optional_html = ''; - let absent_html = ''; - if (data.properties) { - for (let key in data.properties) { - let shape_html = ""; - let response1 = data.properties[key].response; - let response_class = ""; - switch (response1) { - case "present": - response_class = "present"; - break; - case "correct": - response_class = "correct"; - break; - case "missing": - response_class = "missing"; - break; - default: - response_class = "wrong"; - break; - } - if (data.properties[key].necessity == "absent") { - if (response1 == "too many statements") { - response1 = "not allowed"; - } - } - if (!response1) { - response1 = "Not in schema"; - response_class = "notinschema"; - } - if (response1 == null) { - response1 = ""; - shape_html += ''+ key + " - " + data.properties[key].name + '
'; - } else { - shape_html += '' + response1 + ''+ key + " - " + data.properties[key].name + '
'; - } - switch (data.properties[key].necessity){ - case "required": - required_html += shape_html; - break; - case "optional": - optional_html += shape_html; - break; - default: - absent_html += shape_html; - break; - } - if ($("#" + key)[0]) { - $("#" + key + " .wikibase-statementgroupview-property-label").append("
" + data.schema + ": " + response1 + "
"); - } - } - } - required_html += "
'; - - $("#entityCheckResponse" ).append( html ); - - if (data.statements) { - for (var statement in data.statements) { - let response2 = data.statements[statement].response; - if (response2 != "not in schema") { - html = "
" + response2 + ""; - $("div[id='" + statement + "'] .wikibase-toolbar-button-edit").append(html); - } - } - } - if (data.general) { - if (data.general.language) { - let response3 = data.general.language; - if (response3 != "not in schema") { - html = "" + response3 + ""; - $("span[class='language-lexical-category-widget_language']").append(html); - } - } - if (data.general.lexicalCategory) { - let response4 = data.general.lexicalCategory; - if (response4 != "not in schema") { - html = "" + response4 + ""; - $("span[class='language-lexical-category-widget_lexical-category']").append(html); - } - } - } - }, - error: function(data) { - $("#entityCheckResponse").append( 'Unable to validate schema' ); - } - }); -} - -function entitycheck_parseResult(data) { - let property = []; - let html = ' Fail'; - let no_matching_triples = "No matching triples found for predicate"; - if (data.reason.includes(no_matching_triples)) { - property = data.reason.match(/P\d+/g); - } - if (property !== null) { - property = property.reduce(function(a,b) { - if (a.indexOf(b) < 0) { - a.push(b); - } - return a; - },[]); - for (let i = 0; i < property.length; i++) { - if (property[i].length > 100) { - property[i] = property[i].substr(0,100) + "…" - } - html += ' Missing valid ' + property[i] + ''; - } - } - html += ""; - return html; -} - -function entitycheck_getStylesheet() { - let stylesheet = "#schemaSearchButton { background-position: center center; background-repeat: no-repeat; position: absolute; top:0; right:0; overflow: hidden; height:100%; background-color: #1E90FF !important; color: #FFFFFF !important; padding: 2px !important}"; - stylesheet += "#entityToCheck { padding: 1em; margin:0; width: 100%;}"; - stylesheet += ".response { padding: 2px;}"; - stylesheet += "a.is_entitycheck-present { color: #008800; }"; - stylesheet += "a.is_entitycheck-allowed { color: #008800; }"; - stylesheet += "a.is_entitycheck-correct { color: #00CC00; }"; - stylesheet += "a.is_entitycheck-missing { color: #FF5500; }"; - stylesheet += "a.is_entitycheck-notinschema { color: #FF5500; }"; - stylesheet += "a.is_entitycheck-wrong { color: #CC0000; }"; - stylesheet += "a.is_entitycheck-wrong_amount { color: #CC0000; }"; - stylesheet += "a.is_entitycheck-incorrect { color: #CC0000; }"; - stylesheet += ".entitycheck_table {vertical-align: top; width: 33%;} "; - stylesheet += ".entitycheck-missing { background-color: #FF8C00; }"; - stylesheet += ".entitycheck-notinschema { background-color: #FF8C00; }"; - stylesheet += ".entitycheck-wrong { background-color: #CC0000; }"; - stylesheet += ".entitycheck-incorrect { background-color: #CC0000; }"; - stylesheet += ".entitycheck-wrong_amount { background-color: #CC0000; }"; - stylesheet += ".entitycheck-excess { background-color: #CC0000; }"; - stylesheet += ".entitycheck-deficit { background-color: #CC0000; }"; - stylesheet += ".entitycheck-present { background-color: #008800; }"; - stylesheet += ".entitycheck-allowed { background-color: #008800; }"; - stylesheet += ".entitycheck-correct { background-color: #00CC00; }"; - stylesheet += ".required .entitycheck-missing { background-color: #FF0000;}"; - stylesheet += ".required a.is_entitycheck-missing { color: #FF0000;}"; - stylesheet += ".absent .entitycheck-missing { display: none;}"; - stylesheet += ".absent a.is_entitycheck-missing { display: none;}"; - stylesheet += ".entitycheck-span { color: #ffffff; padding:2px; margin: 2px; font-size:75%; border-radius:2px; }"; - return stylesheet; -} -}()); diff --git a/sonar-project.properties b/sonar-project.properties deleted file mode 100644 index 1586f08..0000000 --- a/sonar-project.properties +++ /dev/null @@ -1,11 +0,0 @@ -sonar.organization=teester -sonar.projectKey=Teester_entityshape - -# relative paths to source directories. More details and properties are described -# in https://sonarcloud.io/documentation/project-administration/narrowing-the-focus/ -sonar.python.version=3 -sonar.sources=. -sonar.dynamicAnalysis=reuseReports -sonar.core.codeCoveragePlugin=cobertura -sonar.python.coverage.reportPaths=*coverage*.xml -sonar.python.xunit.reportPath=xunit-result*.xml diff --git a/test_schemas.py b/test_schemas.py deleted file mode 100644 index dbbab96..0000000 --- a/test_schemas.py +++ /dev/null @@ -1,240 +0,0 @@ -""" -Tests to test wikidata entityschemas against wikidata items -""" -import unittest - -import requests - -from app import app - - -class MyTestCase(unittest.TestCase): - """ - Testcases to test wikidata entityschemas against wikidata items - """ - def setUp(self) -> None: - app.config["TESTING"] = True - self.app = app.test_client() - - def tearDown(self) -> None: - # We don't need to tear anything down after the test - pass - - def test_specific_wikidata_item_against_schema(self): - """ - Tests a specific entity against a certain schema and checks that - a statements and a properties response are returned - """ - test_pairs: dict = {"E236": "Q1728820"} - - for key in test_pairs: - with self.subTest(key=key): - value = test_pairs[key] - response = self.app.get(f'/api?entityschema={key}&entity={value}&language=en', - follow_redirects=True) - self.assertIsNotNone(response.json["statements"]) - self.assertIsNotNone(response.json["properties"]) - - def test_lexical_category(self): - """ - This test checks that a lexicalCategory response is returned when a - lexeme is tested against a schema looking for a lexical category - """ - test_pairs: dict = {"E56": "Lexeme:L42"} - for key in test_pairs: - with self.subTest(key=key): - value = test_pairs[key] - response = self.app.get(f'/api?entityschema={key}&entity={value}&language=en', - follow_redirects=True) - self.assertIsNotNone(response.json["general"]["lexicalCategory"]) - self.assertIsNotNone(response.json["general"]["language"]) - - def test_wikidata_entityschemas(self) -> None: - """ - Tests all wikidata entityschemas return 200 - - This test iterates through each entityschema on wikidata and checks to see that - each one returns a "200" code when compared with a specific entity - The "200" code tells us that the entityschema was successfully compared - with the entity, but not whether it passes or not. This will give us a list of - entityschemas that we have problems with. This may be due to a bug in entityshape - or a problem with the entityschema itself. - """ - skips: list = ["E59", "E70", "E93", "E123", "E165", - "E245", "E246", "E247", "E251", "E999"] - url: str = "https://www.wikidata.org/w/api.php?" \ - "action=query&format=json&list=allpages&aplimit=max&apnamespace=640" - response = requests.get(url) - json_text: dict = response.json() - entity_schemas: list = [] - for schema in json_text["query"]["allpages"]: - entity_schemas.append(schema["title"][13:]) - for schema in entity_schemas: - with self.subTest(schema=schema): - if schema in skips: - self.skipTest(f"Schema {schema} not supported") - print(schema) - response = self.app.get(f'/api?entityschema={schema}&entity=Q100532807&language=en', - follow_redirects=True) - self.assertEqual(response.status_code, 200) - - def test_specific_entityschema(self) -> None: - """ - This test tests a specified schema against an entity and checks that a 200 response - is received - """ - schema: str = "E236" - response = self.app.get(f'/api?entityschema={schema}&entity=Q100532807&language=en', - follow_redirects=True) - self.assertEqual(200, response.status_code) - self.assertEqual("Member of the Oireachtas", response.json["name"]) - self.assertEqual({'name': 'occupation', 'necessity': 'required', 'response': 'missing'}, - response.json["properties"]["P106"]) - - def test_entityschema_e236(self): - """ - Tests all properties of Q1728820 pass E236 - - This test tests entityschema E236 (Member of the Oireachtas) against entity - Q1728820 (Leo Varadkar). All properties in the schema should pass - """ - response = self.app.get('/api?entityschema=E236&entity=Q1728820&language=en', - follow_redirects=True) - self.assertEqual(200, response.status_code) - properties: list = ["P102", "P18", "P31", "P734", "P735", "P39", "P21", - "P27", "P106", "P569", "P4690"] - for prop in properties: - with self.subTest(prop=prop): - self.assertTrue(response.json["properties"][prop]["response"] - in ["correct", "present"]) - - def test_entityschema_e297(self): - """ - Tests item with repeated properties works correctly - - This test tests entityschema E297 (sailboat class) against entity - Q97179551 (J/92s). The schema has multiple statements about the same property. - The test checks to ensure that the correct cardinality is calculated for - the relevant property - """ - response = self.app.get('/api?entityschema=E297&entity=Q97179551&language=en', - follow_redirects=True) - self.assertEqual(200, response.status_code) - properties: list = ["P2043", "P2067"] - for prop in properties: - with self.subTest(prop=prop): - self.assertTrue(response.json["properties"][prop]["response"] in - ["correct", "present"]) - - def test_entityschema_e295(self): - """ - Tests item with cardinality of 0 evaluates correctly - - This test tests entityschema E295 (townland) against entity Q85396849 (Drumlohan). - The schema has a P361 (part of) with a cardinality of 0, meaning the item should - not contain any P361. The test checks that the response is false for this item - """ - response = self.app.get('/api?entityschema=E295&entity=Q85396849&language=en', - follow_redirects=True) - self.assertEqual(200, response.status_code) - properties: list = ["P361"] - for prop in properties: - with self.subTest(prop=prop): - self.assertTrue(response.json["properties"][prop]["response"] in - ["too many statements"]) - self.assertTrue(response.json["properties"][prop]["necessity"] in - ["absent"]) - - def test_entityschema_e300(self): - """ - Tests item with optional qualifiers is evaluated correctly - - This test tests entityschema E300 (auto racing series) against entity Q92821370 - (2022 FIA Formula One Season). The schema has a P3450 (Sports league of competition) - with optional qualifiers. The test checks that the response is correct for this item - """ - response = self.app.get('/api?entityschema=E300&entity=Q92821370&language=en', - follow_redirects=True) - self.assertEqual(200, response.status_code) - properties: list = ["P3450"] - for prop in properties: - with self.subTest(prop=prop): - self.assertTrue(response.json["properties"][prop]["response"] in ["present"]) - self.assertTrue(response.json["properties"][prop]["necessity"] in ["required"]) - - def test_entityschema_e126(self): - """ - Tests that a comment line # without a space after passes - - This test tests entityschema E126 against entity Q85396849. - The schema has a comment line that starts with #{ rather than # {. - The test checks that we get a 200 response - """ - response = self.app.get('/api?entityschema=E126&entity=Q85396849&language=en', - follow_redirects=True) - self.assertEqual(200, response.status_code) - - def test_entityschema_e278(self): - """ - Tests that {} containing non cardinalities arent evaluated as cardinalities - - This test tests entityschema E278 against entity Q85396849 (Drumlohan). - The schema contains a line ps:P279 { wdt:P31 wd:Q67101749 }. The test makes - sure that we get a 200 and that the contents of the {} aren't - evaluated as a cardinality - """ - response = self.app.get('/api?entityschema=E278&entity=Q85396849&language=en', - follow_redirects=True) - self.assertEqual(200, response.status_code) - - def test_entityschema_e3(self): - """ - Tests that shapes with [] are evaluated - - This test tests entityschema E3 against entity Q85396849 (Drumlohan). - The schema has a shape [ ]. The test makes sure - that the schema returns a 200 response, indicating that it evaluates [] - """ - response = self.app.get('/api?entityschema=E3&entity=Q85396849&language=en', - follow_redirects=True) - self.assertEqual(200, response.status_code) - - def test_entityschema_e176(self): - """ - Tests that where and { are on different lines, it works - - This test tests entityschema E176 against entity Q85396849 (Drumlohan). - The schema contains a shape where the name and { are on different lines. - The test checks that this evaluates correctly - """ - response = self.app.get('/api?entityschema=E176&entity=Q85396849&language=en', - follow_redirects=True) - self.assertEqual(200, response.status_code) - - def test_entityschema_e187(self): - """ - Tests that shapes with no brackets work - - This test tests entityschema E187 against entity Q85396849 (Drumlohan). - The schema has a shape of the form geo:Literal. The test - makes sure that this evaluates - """ - response = self.app.get('/api?entityschema=E187&entity=Q85396849&language=en', - follow_redirects=True) - self.assertEqual(200, response.status_code) - - def test_entityschema_e275(self): - """ - Tests that schemas importing other schemas don't fail - - This test tests entityschema E275 against entity Q85396849 (Drumlohan). - The schema imports another schema and references it. The test makes sure - that this still returns a 200 response - """ - response = self.app.get('/api?entityschema=E275&entity=Q85396849&language=en', - follow_redirects=True) - self.assertEqual(200, response.status_code) - - -if __name__ == '__main__': - unittest.main() diff --git a/tests/__init__.py b/tests/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/test_entityshape.py b/tests/test_entityshape.py new file mode 100644 index 0000000..71c620f --- /dev/null +++ b/tests/test_entityshape.py @@ -0,0 +1,125 @@ +from unittest import TestCase + +# from rich.console import Console +from entityshape import EidError, EntityShape, LangError, QidError, Result + + +class TestEntityShape(TestCase): + def test_get_result_invalid_eid(self): + e = EntityShape(eid="eeeE1", lang="en", entity_id="Q1") + with self.assertRaises(EidError): + e.validate_and_get_result() + # print(e.result) + # assert e.result != {} + + def test_get_result_invalid_lang(self): + """The API does not seem to check whether the lang code is valid or not""" + e = EntityShape(eid="E376", lang="", entity_id="Q119853967") + with self.assertRaises(LangError): + e.validate_and_get_result() + # assert e.result.is_valid is True + # print(e.result) + # assert e.result != {} + + def test_get_result_invalid_entity_id(self): + e = EntityShape(eid="E1", lang="en", entity_id="qqqqQ1") + with self.assertRaises(QidError): + e.validate_and_get_result() + # print(e.result) + # assert e.result != {} + + # TODO this fails because of ? + # def test_get_result_valid(self): + # e = EntityShape(eid="E376", lang="en", qid="Q96308969") + # e.get_result() + # print(e.result) + # assert isinstance(e.result, Result) + # assert e.result.is_valid is True + + def test_get_result_invalid_missing_operator(self): + e = EntityShape(eid="E376", lang="en", entity_id="Q96308969") + e.validate_and_get_result() + # print(e.result) + assert isinstance(e.result, Result) + assert e.result.is_valid is False + assert e.result.required_properties_that_are_missing == {"P137"} + + # this fails because of https://github.com/dpriskorn/entityshape/issues/2 + # def test_get_result_invalid_wrong_schema(self): + # e = EntityShape(eid="E375", lang="en", entity_id="Q119853967") + # e.get_result() + # print(e.result) + # assert isinstance(e.result, Result) + # assert e.result.is_valid is False + # assert e.result.required_properties_that_are_missing == {"P2043"} + # assert e.result.properties_that_are_not_allowed == {"P912", "P625", "P276"} + + def test_get_result_missing_statement_response(self): + e = EntityShape(eid="E395", lang="en", entity_id="Q4802448") + e.validate_and_get_result() + # print(e.compare_shape_result) + # console = Console() + # console.print(e.result) + assert isinstance(e.result, Result) + assert e.result.is_valid is False + assert e.result.properties_without_enough_correct_statements == {"P39"} + + def test_get_result_weird_statement_response_party_person(self): + e = EntityShape(eid="E395", lang="en", entity_id="Q20727") + e.validate_and_get_result() + assert isinstance(e.result, Result) + assert e.result.is_valid is True + + def test_lexeme(self): + # wbi_config['USER_AGENT'] = 'WikibaseIntegrator in PAWS by So9q' + # wbi = WikibaseIntegrator() + # # This query was build in a few seconds using https://query.wikidata.org/querybuilder/?uselang=en :) + # results = execute_sparql_query(""" + # SELECT ?lexemeId ?lemma WHERE { + # ?lexemeId dct:language wd:Q9027; + # wikibase:lexicalCategory wd:Q1084; + # wikibase:lemma ?lemma. + # } + # limit 10 + # """) + # bindings = results["results"]["bindings"] + # print(f"Found {len(bindings)} results") + # count = 1 + # for result in bindings: + # print(result) + # # Get the entity id from the URI + # entity_id = result["lexemeId"]["value"].split("/")[-1] + # print(f"Working on: {entity_id}") + # entity = wbi.lexeme.get(entity_id) + e = EntityShape(entity_id="L41172", eid="E34", lang="en") + e.validate_and_get_result() + print(e.result) + # try: + # result = e.validate_and_get_result() + # # Ignore the invalid shelters missing an operator P137 + # if result.is_valid is False and result.required_properties_that_are_missing == {"P137"}: + # print("Skipping item only missing an operator") + # # Ignore the invalid items with too many P625 + # elif result.is_valid is False and result.properties_with_too_many_statements == {"P625"}: + # print("Skipping item that only invalidates because it has a coordinate") + # elif result.is_valid is True: + # print("Skipping valid item - they are boring!") + # else: + # print(repr(result) + f"\nSee {entity.get_entity_url()}") + # except KeyError: + # print( + # f"Got a keyerror for the entity {entity_id}, this is a known bug with entityshape, see https://github.com/dpriskorn/entityshape/issues/2") + + def test_labels_from_custom_wikibase_valid_qid(self): + eid = "E1" # see https://furry.wikibase.cloud/wiki/EntitySchema:E1 + wikibase_url = "https://furry.wikibase.cloud" + mediawiki_api_url = "https://furry.wikibase.cloud/w/api.php" + e = EntityShape( + entity_id="Q3", + eid=eid, + lang="en", + mediawiki_api_url=mediawiki_api_url, + wikibase_url=wikibase_url, + ) + e.validate_and_get_result() + print(e.result) diff --git a/tests/test_result.py b/tests/test_result.py new file mode 100644 index 0000000..4957dd6 --- /dev/null +++ b/tests/test_result.py @@ -0,0 +1,1218 @@ +from typing import Any, Dict +from unittest import TestCase + +from entityshape import Result + +hiking_path_with_1_missing_required_property: Dict[Any, Any] = { + "error": "", + "general": {}, + "name": "hiking path", + "properties": { + "P10467": { + "name": "naturkartan.se ID", + "necessity": "optional", + "response": "missing", + }, + "P112": { + "name": "founded by", + "necessity": "optional", + "response": "missing", + }, + "P131": { + "name": "located in the administrative territorial entity", + "necessity": "required", + "response": "present", + }, + "P1343": { + "name": "described by source", + "necessity": "optional", + "response": "missing", + }, + "P137": { + "name": "operator", + "necessity": "optional", + "response": "missing", + }, + "P138": { + "name": "named after", + "necessity": "optional", + "response": "missing", + }, + "P1427": { + "name": "start point", + "necessity": "optional", + "response": "missing", + }, + "P1444": { + "name": "destination point", + "necessity": "optional", + "response": "missing", + }, + "P15": { + "name": "route map", + "necessity": "optional", + "response": "missing", + }, + "P1545": { + "name": "series ordinal", + "necessity": "optional", + "response": "missing", + }, + "P1552": { + "name": "has quality", + "necessity": "optional", + "response": "missing", + }, + "P1589": { + "name": "lowest point", + "necessity": "optional", + "response": "missing", + }, + "P17": {"name": "country", "necessity": "required", "response": "present"}, + "P18": {"name": "image", "necessity": "optional", "response": "missing"}, + "P1997": { + "name": "Facebook Places ID", + "necessity": "optional", + "response": "missing", + }, + "P2043": {"name": "length", "necessity": "required", "response": "missing"}, + "P206": { + "name": "located in or next to body of water", + "necessity": "optional", + "response": "missing", + }, + "P214": {"name": "VIAF ID", "necessity": "optional", "response": "missing"}, + "P2347": {"name": "YSO ID", "necessity": "optional", "response": "missing"}, + "P242": { + "name": "locator map image", + "necessity": "optional", + "response": "missing", + }, + "P2671": { + "name": "Google Knowledge Graph ID", + "necessity": "optional", + "response": "missing", + }, + "P2789": { + "name": "connects with", + "necessity": "optional", + "response": "missing", + }, + "P30": { + "name": "continent", + "necessity": "optional", + "response": "missing", + }, + "P3018": { + "name": "located in protected area", + "necessity": "optional", + "response": "missing", + }, + "P31": { + "name": "instance of", + "necessity": "optional", + "response": "correct", + }, + "P3173": { + "name": "offers view on", + "necessity": "optional", + "response": "missing", + }, + "P361": {"name": "part of", "necessity": "optional", "response": "missing"}, + "P373": { + "name": "Commons category", + "necessity": "optional", + "response": "missing", + }, + "P402": { + "name": "OpenStreetMap relation ID", + "necessity": "optional", + "response": "missing", + }, + "P4552": { + "name": "mountain range", + "necessity": "optional", + "response": "missing", + }, + "P527": { + "name": "has part(s)", + "necessity": "optional", + "response": "missing", + }, + "P559": { + "name": "terminus", + "necessity": "optional", + "response": "missing", + }, + "P571": { + "name": "inception", + "necessity": "optional", + "response": "missing", + }, + "P609": { + "name": "terminus location", + "necessity": "optional", + "response": "missing", + }, + "P610": { + "name": "highest point", + "necessity": "optional", + "response": "missing", + }, + "P6104": { + "name": "maintained by WikiProject", + "necessity": "optional", + "response": "missing", + }, + "P625": { + "name": "coordinate location", + "necessity": "absent", + "response": "missing", + }, + "P646": { + "name": "Freebase ID", + "necessity": "optional", + "response": "missing", + }, + "P691": { + "name": "NL CR AUT ID", + "necessity": "optional", + "response": "missing", + }, + "P706": { + "name": "located in/on physical feature", + "necessity": "optional", + "response": "missing", + }, + "P7127": { + "name": "AllTrails trail ID", + "necessity": "optional", + "response": "missing", + }, + "P7252": { + "name": "degree of difficulty", + "necessity": "optional", + "response": "missing", + }, + "P856": { + "name": "official website", + "necessity": "optional", + "response": "missing", + }, + "P910": { + "name": "topic's main category", + "necessity": "optional", + "response": "missing", + }, + "P973": { + "name": "described at URL", + "necessity": "optional", + "response": "present", + }, + }, + "schema": "E375", + "statements": { + "Q119845590$11794598-A67C-4978-8D1D-359B90E5EE15": { + "necessity": "optional", + "property": "P973", + "response": "allowed", + }, + "Q119845590$1CEFCE58-826A-4243-A9D2-CF47BF9F493E": { + "necessity": "required", + "property": "P131", + "response": "allowed", + }, + "Q119845590$72CEF06C-C5AE-40DE-AB61-D530914AFD4E": { + "necessity": "optional", + "property": "P31", + "response": "correct", + }, + "Q119845590$9C7FBBB4-7302-423F-AF51-C46A07F680CD": { + "necessity": "required", + "property": "P131", + "response": "allowed", + }, + "Q119845590$D036FE30-1DFD-461A-8AA2-AA0CB977E22D": { + "necessity": "required", + "property": "P17", + "response": "allowed", + }, + }, + "validity": {}, +} # P2043 length is missing +campsite_missing_correct_p31: Dict[Any, Any] = { + "error": "", + "general": {}, + "name": "campsite", + "properties": { + "P10467": { + "name": "naturkartan.se ID", + "necessity": "optional", + "response": "missing", + }, + "P11177": { + "name": "Camp Wild ID", + "necessity": "optional", + "response": "missing", + }, + "P131": { + "name": "located in the administrative territorial entity", + "necessity": "required", + "response": "present", + }, + "P137": { + "name": "operator", + "necessity": "required", + "response": "present", + }, + "P138": { + "name": "named after", + "necessity": "optional", + "response": "missing", + }, + "P1448": { + "name": "official name", + "necessity": "optional", + "response": "missing", + }, + "P17": {"name": "country", "necessity": "required", "response": "present"}, + "P18": {"name": "image", "necessity": "optional", "response": "missing"}, + "P206": { + "name": "located in or next to body of water", + "necessity": "optional", + "response": "missing", + }, + "P242": { + "name": "locator map image", + "necessity": "optional", + "response": "missing", + }, + "P2670": { + "name": "has part(s) of the class", + "necessity": "optional", + "response": "missing", + }, + "P276": { + "name": "location", + "necessity": "optional", + "response": "present", + }, + "P30": { + "name": "continent", + "necessity": "optional", + "response": "missing", + }, + "P3018": { + "name": "located in protected area", + "necessity": "optional", + "response": "missing", + }, + "P31": { + "name": "instance of", + "necessity": "required", + "response": "not enough correct statements", + }, + "P3173": { + "name": "offers view on", + "necessity": "optional", + "response": "missing", + }, + "P373": { + "name": "Commons category", + "necessity": "optional", + "response": "missing", + }, + "P402": { + "name": "OpenStreetMap relation ID", + "necessity": "optional", + "response": "missing", + }, + "P4552": { + "name": "mountain range", + "necessity": "optional", + "response": "missing", + }, + "P527": { + "name": "has part(s)", + "necessity": "optional", + "response": "missing", + }, + "P571": { + "name": "inception", + "necessity": "optional", + "response": "missing", + }, + "P5775": { + "name": "image of interior", + "necessity": "absent", + "response": "missing", + }, + "P625": { + "name": "coordinate location", + "necessity": "required", + "response": "present", + }, + "P706": { + "name": "located in/on physical feature", + "necessity": "optional", + "response": "missing", + }, + "P7418": { + "name": "image of frontside", + "necessity": "optional", + "response": "missing", + }, + "P8517": {"name": "view", "necessity": "optional", "response": "missing"}, + "P856": { + "name": "official website", + "necessity": "optional", + "response": "missing", + }, + "P912": { + "name": "has facility", + "necessity": "optional", + "response": "missing", + }, + "P9676": { + "name": "Vindskyddskartan.se ID", + "necessity": "optional", + "response": "missing", + }, + "P973": { + "name": "described at URL", + "necessity": "optional", + "response": "present", + }, + }, + "schema": "E376", + "statements": { + "Q119853974$20823AE2-6F1A-43FB-9C9C-A6C2CC772446": { + "necessity": "required", + "property": "P137", + "response": "allowed", + }, + "Q119853974$3F1F91A9-693C-41AA-A5C7-54E711D83594": { + "necessity": "required", + "property": "P17", + "response": "allowed", + }, + "Q119853974$4A29484F-6C84-4B5A-B9E9-25344162CFCB": { + "necessity": "required", + "property": "P625", + "response": "allowed", + }, + "Q119853974$4BCAA6A7-8857-4056-B286-7B646A2D72C9": { + "necessity": "required", + "property": "P131", + "response": "allowed", + }, + "Q119853974$8E8612BE-78F7-463A-8E35-96F53277E3B4": { + "necessity": "optional", + "property": "P276", + "response": "allowed", + }, + "Q119853974$AA270DBF-B117-45DD-AF68-8F1A7AFC56F1": { + "necessity": "required", + "property": "P31", + "response": "allowed", + }, + "Q119853974$C25AF244-F0D9-4E19-8126-42092C0FA4FB": { + "necessity": "optional", + "property": "P973", + "response": "allowed", + }, + "Q119853974$CD71CE83-F045-486B-873F-F7A8FB437E2A": { + "necessity": "required", + "property": "P131", + "response": "allowed", + }, + }, + "validity": {}, +} +campsite_not_allowed_p625: Dict[Any, Any] = { + "error": "", + "general": {}, + "name": "hiking path", + "properties": { + "P10467": { + "name": "naturkartan.se ID", + "necessity": "optional", + "response": "missing", + }, + "P112": { + "name": "founded by", + "necessity": "optional", + "response": "missing", + }, + "P131": { + "name": "located in the administrative territorial entity", + "necessity": "required", + "response": "present", + }, + "P1343": { + "name": "described by source", + "necessity": "optional", + "response": "missing", + }, + "P137": { + "name": "operator", + "necessity": "optional", + "response": "present", + }, + "P138": { + "name": "named after", + "necessity": "optional", + "response": "missing", + }, + "P1427": { + "name": "start point", + "necessity": "optional", + "response": "missing", + }, + "P1444": { + "name": "destination point", + "necessity": "optional", + "response": "missing", + }, + "P15": { + "name": "route map", + "necessity": "optional", + "response": "missing", + }, + "P1545": { + "name": "series ordinal", + "necessity": "optional", + "response": "missing", + }, + "P1552": { + "name": "has quality", + "necessity": "optional", + "response": "missing", + }, + "P1589": { + "name": "lowest point", + "necessity": "optional", + "response": "missing", + }, + "P17": {"name": "country", "necessity": "required", "response": "present"}, + "P18": {"name": "image", "necessity": "optional", "response": "missing"}, + "P1997": { + "name": "Facebook Places ID", + "necessity": "optional", + "response": "missing", + }, + "P2043": {"name": "length", "necessity": "required", "response": "missing"}, + "P206": { + "name": "located in or next to body of water", + "necessity": "optional", + "response": "missing", + }, + "P214": {"name": "VIAF ID", "necessity": "optional", "response": "missing"}, + "P2347": {"name": "YSO ID", "necessity": "optional", "response": "missing"}, + "P242": { + "name": "locator map image", + "necessity": "optional", + "response": "missing", + }, + "P2671": { + "name": "Google Knowledge Graph ID", + "necessity": "optional", + "response": "missing", + }, + "P276": {"name": "location", "necessity": "absent"}, + "P2789": { + "name": "connects with", + "necessity": "optional", + "response": "missing", + }, + "P30": { + "name": "continent", + "necessity": "optional", + "response": "missing", + }, + "P3018": { + "name": "located in protected area", + "necessity": "optional", + "response": "missing", + }, + "P31": { + "name": "instance of", + "necessity": "optional", + "response": "present", + }, + "P3173": { + "name": "offers view on", + "necessity": "optional", + "response": "missing", + }, + "P361": {"name": "part of", "necessity": "optional", "response": "missing"}, + "P373": { + "name": "Commons category", + "necessity": "optional", + "response": "missing", + }, + "P402": { + "name": "OpenStreetMap relation ID", + "necessity": "optional", + "response": "missing", + }, + "P4552": { + "name": "mountain range", + "necessity": "optional", + "response": "missing", + }, + "P527": { + "name": "has part(s)", + "necessity": "optional", + "response": "present", + }, + "P559": { + "name": "terminus", + "necessity": "optional", + "response": "missing", + }, + "P571": { + "name": "inception", + "necessity": "optional", + "response": "missing", + }, + "P609": { + "name": "terminus location", + "necessity": "optional", + "response": "missing", + }, + "P610": { + "name": "highest point", + "necessity": "optional", + "response": "missing", + }, + "P6104": { + "name": "maintained by WikiProject", + "necessity": "optional", + "response": "missing", + }, + "P625": { + "name": "coordinate location", + "necessity": "absent", + "response": "too many statements", + }, + "P646": { + "name": "Freebase ID", + "necessity": "optional", + "response": "missing", + }, + "P691": { + "name": "NL CR AUT ID", + "necessity": "optional", + "response": "missing", + }, + "P706": { + "name": "located in/on physical feature", + "necessity": "optional", + "response": "missing", + }, + "P7127": { + "name": "AllTrails trail ID", + "necessity": "optional", + "response": "missing", + }, + "P7252": { + "name": "degree of difficulty", + "necessity": "optional", + "response": "missing", + }, + "P856": { + "name": "official website", + "necessity": "optional", + "response": "missing", + }, + "P910": { + "name": "topic's main category", + "necessity": "optional", + "response": "missing", + }, + "P912": {"name": "has facility", "necessity": "absent"}, + "P973": { + "name": "described at URL", + "necessity": "optional", + "response": "present", + }, + }, + "schema": "E375", + "statements": { + "Q119853967$36193CA1-6DF5-4DAC-910E-0319812671FB": { + "necessity": "required", + "property": "P131", + "response": "allowed", + }, + "Q119853967$43477af1-49e1-e0fd-8672-fbad6c66dd96": { + "necessity": "optional", + "property": "P527", + "response": "allowed", + }, + "Q119853967$5C65C2A6-DA72-4C71-83E2-0C626C906506": { + "property": "P276", + "response": "not in schema", + }, + "Q119853967$5DF7E848-400D-4AEE-B2AD-A06D92ACA2DD": { + "necessity": "absent", + "property": "P625", + "response": "allowed", + }, + "Q119853967$736c7261-4e8c-e754-e817-e8b253ae5e7b": { + "property": "P912", + "response": "not in schema", + }, + "Q119853967$7A4E524F-12CE-40C1-B709-4876A2C465D6": { + "necessity": "optional", + "property": "P137", + "response": "allowed", + }, + "Q119853967$7F83F435-83AC-4210-A717-ED3DE033319D": { + "necessity": "required", + "property": "P17", + "response": "allowed", + }, + "Q119853967$874d280c-427c-8c9d-8ccb-3bd107e7d6a4": { + "necessity": "optional", + "property": "P527", + "response": "allowed", + }, + "Q119853967$C30A3085-F06D-4834-A887-CDDBC314EE7F": { + "necessity": "required", + "property": "P131", + "response": "allowed", + }, + "Q119853967$C98E6C4A-98F8-47EA-8791-C4C85262FC9B": { + "necessity": "optional", + "property": "P973", + "response": "allowed", + }, + "Q119853967$D8462BF0-2599-4F62-8147-E84465529B19": { + "necessity": "optional", + "property": "P31", + "response": "allowed", + }, + }, + "validity": {}, +} +party_member_missing_p37: Dict[Any, Any] = { + "general": {}, + "properties": { + "P21": { + "name": "sex or gender", + "necessity": "required", + "response": "correct", + }, + "P31": {"name": "instance of", "necessity": "required", "response": "correct"}, + "P102": { + "name": "member of political party", + "necessity": "required", + "response": "present", + }, + "P569": { + "name": "date of birth", + "necessity": "required", + "response": "present", + }, + "P570": { + "name": "date of death", + "necessity": "optional", + "response": "present", + }, + "P27": { + "name": "country of citizenship", + "necessity": "required", + "response": "correct", + }, + "P106": {"name": "occupation", "necessity": "required", "response": "correct"}, + "P735": {"name": "given name", "necessity": "required", "response": "present"}, + "P18": {"name": "image", "necessity": "required", "response": "present"}, + "P1343": { + "name": "described by source", + "necessity": "required", + "response": "present", + }, + "P39": { + "name": "position held", + "necessity": "optional", + "response": "not enough correct statements", + }, + "P119": { + "name": "place of burial", + "necessity": "optional", + "response": "present", + }, + "P1412": { + "name": "languages spoken, written or signed", + "necessity": "required", + "response": "correct", + }, + "P937": { + "name": "work location", + "necessity": "required", + "response": "correct", + }, + "P1559": {"name": "name in native language", "necessity": "absent"}, + "P3222": {"name": "NE.se ID", "necessity": "absent"}, + "P19": { + "name": "place of birth", + "necessity": "required", + "response": "present", + }, + "P20": { + "name": "place of death", + "necessity": "optional", + "response": "present", + }, + "P734": {"name": "family name", "necessity": "required", "response": "present"}, + "P373": { + "name": "Commons category", + "necessity": "required", + "response": "present", + }, + "P4602": {"name": "date of burial or cremation", "necessity": "absent"}, + "P646": {"name": "Freebase ID", "necessity": "absent"}, + "P4819": { + "name": "Swedish Portrait Archive ID", + "necessity": "required", + "response": "present", + }, + "P3368": {"name": "Prabook ID", "necessity": "absent"}, + "P2600": { + "name": "Geni.com profile ID", + "necessity": "optional", + "response": "present", + }, + "P2561": {"name": "name", "necessity": "absent"}, + "P551": {"name": "residence", "necessity": "optional", "response": "present"}, + "P509": { + "name": "cause of death", + "necessity": "optional", + "response": "present", + }, + "P535": { + "name": "Find a Grave memorial ID", + "necessity": "optional", + "response": "present", + }, + "P109": {"name": "signature", "necessity": "optional", "response": "present"}, + "P1442": { + "name": "image of grave", + "necessity": "optional", + "response": "present", + }, + "P1196": { + "name": "manner of death", + "necessity": "optional", + "response": "missing", + }, + "P3373": {"name": "sibling", "necessity": "optional", "response": "missing"}, + "P22": {"name": "father", "necessity": "optional", "response": "missing"}, + "P25": {"name": "mother", "necessity": "optional", "response": "missing"}, + "P40": {"name": "child", "necessity": "optional", "response": "missing"}, + "P97": {"name": "noble title", "necessity": "optional", "response": "missing"}, + "P5056": { + "name": "patronym or matronym for this person", + "necessity": "optional", + "response": "missing", + }, + "P512": { + "name": "academic degree", + "necessity": "optional", + "response": "missing", + }, + "P69": {"name": "educated at", "necessity": "optional", "response": "missing"}, + "P2949": { + "name": "WikiTree person ID", + "necessity": "optional", + "response": "missing", + }, + "P9713": { + "name": "Swedish National Archive agent ID", + "necessity": "optional", + "response": "missing", + }, + "P485": {"name": "archives at", "necessity": "optional", "response": "missing"}, + "P9495": { + "name": "National Historical Museums of Sweden ID", + "necessity": "optional", + "response": "missing", + }, + "P5101": { + "name": "Swedish Literature Bank Author ID", + "necessity": "optional", + "response": "missing", + }, + "P3217": { + "name": "Dictionary of Swedish National Biography ID", + "necessity": "optional", + "response": "missing", + }, + "P4963": { + "name": "Svenskt kvinnobiografiskt lexikon ID", + "necessity": "optional", + "response": "missing", + }, + "P6821": { + "name": "Uppsala University Alvin ID", + "necessity": "optional", + "response": "missing", + }, + "P5587": {"name": "Libris-URI", "necessity": "optional", "response": "missing"}, + "P7847": { + "name": "DigitaltMuseum ID", + "necessity": "optional", + "response": "missing", + }, + "P1248": { + "name": "KulturNav-ID", + "necessity": "optional", + "response": "missing", + }, + "P5259": { + "name": "Swedish Gravestone ID", + "necessity": "optional", + "response": "missing", + }, + "P3618": { + "name": "base salary", + "necessity": "optional", + "response": "missing", + }, + }, + "statements": { + "q4802448$2B153142-5757-45BD-BB8E-EDBD6C505394": { + "property": "P21", + "necessity": "required", + "response": "correct", + }, + "Q4802448$6F4E8AEC-14A1-4AD0-AF79-AC26414E154C": { + "property": "P31", + "necessity": "required", + "response": "correct", + }, + "Q4802448$0484EB04-FF7F-4E06-8E30-EC45D00D5E6B": { + "property": "P102", + "necessity": "required", + "response": "allowed", + }, + "Q4802448$CE9C69A0-A0D2-498E-8159-3A4144412F64": { + "property": "P569", + "necessity": "required", + "response": "allowed", + }, + "Q4802448$396CFEEE-BDB5-40C3-85D4-A30C9CF6DB56": { + "property": "P570", + "necessity": "optional", + "response": "allowed", + }, + "Q4802448$FFB1A0F8-D15F-41F0-958E-1B43F631558A": { + "property": "P27", + "necessity": "required", + "response": "correct", + }, + "Q4802448$D26A0B14-EA5B-4A9A-A5C0-5A20FD912612": { + "property": "P106", + "necessity": "required", + "response": "correct", + }, + "Q4802448$0B0473FD-7EF8-421D-9C85-C4D2D95F3FA2": { + "property": "P106", + "necessity": "required", + "response": "allowed", + }, + "Q4802448$4DBC9F21-F384-4DCB-B040-A6E5FA45D9D8": { + "property": "P735", + "necessity": "required", + "response": "allowed", + }, + "Q4802448$0277dd51-445e-5014-d5bf-2cffc1eb8e96": { + "property": "P735", + "necessity": "required", + "response": "allowed", + }, + "Q4802448$c5afc898-4b44-1e0f-370b-0952661b2263": { + "property": "P735", + "necessity": "required", + "response": "allowed", + }, + "Q4802448$F2F3B54D-1B41-4760-A129-98F292C4FEA8": { + "property": "P18", + "necessity": "required", + "response": "allowed", + }, + "Q4802448$D767380D-21DD-411B-B742-0FC06FCFAB5A": { + "property": "P1343", + "necessity": "required", + "response": "allowed", + }, + "Q4802448$584F3970-E62D-4F31-A925-2B7D9B3F9420": { + "property": "P1343", + "necessity": "required", + "response": "allowed", + }, + "Q4802448$ed53e939-46b3-9361-db57-9505ebc69970": { + "property": "P1343", + "necessity": "required", + "response": "allowed", + }, + "Q4802448$C2F6DC9B-9E34-436F-92BA-FBAF35678CA0": { + "property": "P39", + "necessity": "optional", + "response": "missing", + }, + "Q4802448$95649645-9282-47BF-B7CE-9F795A2562C0": { + "property": "P119", + "necessity": "optional", + "response": "allowed", + }, + "Q4802448$51FCEBB3-4FB8-4EFB-95B7-0AA89B2F6137": { + "property": "P1412", + "necessity": "required", + "response": "correct", + }, + "Q4802448$7D198758-266A-401A-9AC1-6D6FA5E83FD4": { + "property": "P937", + "necessity": "required", + "response": "correct", + }, + "Q4802448$15A1D0F3-4843-4F01-A4ED-B412AF8D08A5": { + "property": "P1559", + "response": "not in schema", + }, + "Q4802448$2C5BE492-0778-484F-8B9E-4FE2CFA57756": { + "property": "P3222", + "response": "not in schema", + }, + "Q4802448$6ac93515-48dc-1964-cacf-e06c6c4be798": { + "property": "P19", + "necessity": "required", + "response": "allowed", + }, + "Q4802448$cf60fb00-4bf4-bfea-6b42-ace7fc8dc36d": { + "property": "P20", + "necessity": "optional", + "response": "allowed", + }, + "Q4802448$0cb0962d-4d3a-4fdf-d784-d803348586bb": { + "property": "P20", + "necessity": "optional", + "response": "allowed", + }, + "Q4802448$06F15D7F-E0CC-4704-BE7D-F1134E051485": { + "property": "P734", + "necessity": "required", + "response": "allowed", + }, + "Q4802448$04950009-6DAF-42C1-89E2-7093B26DF80C": { + "property": "P373", + "necessity": "required", + "response": "allowed", + }, + "Q4802448$22AA2038-D33F-4DA3-B725-C0A162A9A39D": { + "property": "P4602", + "response": "not in schema", + }, + "Q4802448$FE68BD20-0633-4D7C-9BBB-1A65C60FAD73": { + "property": "P646", + "response": "not in schema", + }, + "Q4802448$501fe9ac-4706-a3f6-224b-3c7da9ac8131": { + "property": "P4819", + "necessity": "required", + "response": "allowed", + }, + "Q4802448$bea3ccd9-4d8f-30c2-684b-e8590cfb7bf0": { + "property": "P4819", + "necessity": "required", + "response": "allowed", + }, + "Q4802448$BA25471E-6D65-4040-B541-04CBB60D8089": { + "property": "P3368", + "response": "not in schema", + }, + "Q4802448$7EB5C10C-AF05-4CD2-9ECE-38C56FB0D72E": { + "property": "P2600", + "necessity": "optional", + "response": "allowed", + }, + "Q4802448$FDE6987A-301E-4E67-B8E0-C035FA45FC63": { + "property": "P2561", + "response": "not in schema", + }, + "Q4802448$db6affac-431e-5f76-6890-b29f00e90397": { + "property": "P551", + "necessity": "optional", + "response": "allowed", + }, + "Q4802448$315e1e37-401d-1460-c876-f31c1071f6ed": { + "property": "P509", + "necessity": "optional", + "response": "allowed", + }, + "Q4802448$8d6aa8d9-4a97-40bc-21fa-f47dbb1ce697": { + "property": "P535", + "necessity": "optional", + "response": "allowed", + }, + "Q4802448$44d2c513-45d2-ebad-2181-32ffea9cf717": { + "property": "P109", + "necessity": "optional", + "response": "allowed", + }, + "Q4802448$103E30B9-C4F0-48DC-9145-8F00F3DB16DF": { + "property": "P1442", + "necessity": "optional", + "response": "allowed", + }, + }, +} +furrywikibase_convention_with_1_missing_required_property: Dict[Any, Any] = { + "error": "", + "general": {}, + "name": "ConFuzzled 2019", + "properties": { + "P1": { + "name": "instance of", + "necessity": "required", + "response": "missing", + } + }, + "schema": "E1", + "statements": {}, + "validity": {}, +} # P1 instance of is missing (this is fabricated for test purposes) + + +class TestResult(TestCase): + def setUp(self) -> None: + self.hiking_path_with_1_missing_required_property_result = Result( + **hiking_path_with_1_missing_required_property + ) + self.hiking_path_with_1_missing_required_property_result.analyze() + self.campsite_missing_correct_p31_result = Result( + **campsite_missing_correct_p31 + ) + self.campsite_missing_correct_p31_result.analyze() + self.campsite_not_allowed_p625_result = Result(**campsite_not_allowed_p625) + self.campsite_not_allowed_p625_result.analyze() + self.party_member_missing_p37_result = Result(**party_member_missing_p37) + self.party_member_missing_p37_result.analyze() + self.furrywikibase_convention_with_1_missing_required_property_result = Result( + **furrywikibase_convention_with_1_missing_required_property + ) + self.furrywikibase_convention_with_1_missing_required_property_result.analyze() + + def test___find_properties_with_too_many_statements__zero(self): + assert ( + len( + self.hiking_path_with_1_missing_required_property_result.properties_with_too_many_statements + ) + == 0 + ) + + def test__find_required_properties__three(self): + assert ( + len( + self.hiking_path_with_1_missing_required_property_result.required_properties + ) + == 3 + ) + + def test__find_missing_properties__(self): + assert ( + len( + self.hiking_path_with_1_missing_required_property_result.missing_properties + ) + == 41 + ) + + def test__find_required_properties_that_are_missing__(self): + assert ( + len( + self.hiking_path_with_1_missing_required_property_result.required_properties_that_are_missing + ) + == 1 + ) + + def test__find_optional_properties_that_are_missing__(self): + assert ( + len( + self.hiking_path_with_1_missing_required_property_result.optional_properties_that_are_missing + ) + == 40 + ) + + def test____find_incorrect_statements__(self): + assert ( + len( + self.hiking_path_with_1_missing_required_property_result.incorrect_statements + ) + == 0 + ) + + def test__find_properties_with_not_enough_correct_statements__(self): + assert ( + len( + self.campsite_missing_correct_p31_result.properties_without_enough_correct_statements + ) + == 1 + ) + assert ( + self.party_member_missing_p37_result.properties_without_enough_correct_statements + == {"P39"} + ) + + def test__find_statements_with_property_that_is_not_allowed__(self): + assert ( + len( + self.campsite_not_allowed_p625_result.statements_with_property_that_is_not_allowed + ) + == 1 + ) + + def test__repr__hiking_path_with_1_missing_required_property_result(self): + assert ( + repr(self.hiking_path_with_1_missing_required_property_result) + == "Valid: False\nRequired properties that are missing: length (P2043)" + ) + + def test__repr__campsite_not_allowed_p625_result(self): + assert repr(self.campsite_not_allowed_p625_result) == ( + "Valid: False\n" + "Properties with too many statements: coordinate location (P625)\n" + "Required properties that are missing: length (P2043)" + ) + + def test__repr__party_member_missing_p37_result(self): + assert repr(self.party_member_missing_p37_result) == ( + "Valid: False\n" + "Properties without enough correct statements: position held (P39)" + ) + + def test__repr__campsite_missing_correct_p31_result(self): + assert ( + repr(self.campsite_missing_correct_p31_result) + == "Valid: False\nProperties without enough correct statements: instance of (P31)" + ) + + def test_danish_labels_in_result_output(self): + self.party_member_missing_p37_result.lang = "da" + assert repr(self.party_member_missing_p37_result) == ( + "Valid: False\nProperties without enough " + "correct statements: embede (P39)" + ) + + def test_furrywikibase_convention_missing_p1(self): + + self.furrywikibase_convention_with_1_missing_required_property_result.wikibase_url = ( + "https://furry.wikibase.cloud" + ) + self.furrywikibase_convention_with_1_missing_required_property_result.mediawiki_api_url = ( + "https://furry.wikibase.cloud/w/api.php" + ) + assert ( + repr(self.furrywikibase_convention_with_1_missing_required_property_result) + == "Valid: False\nRequired properties that are missing: instance of (P1)" + )