diff --git a/.idea/api-editor.iml b/.idea/api-editor.iml index d6ebd4805..fe90391a9 100644 --- a/.idea/api-editor.iml +++ b/.idea/api-editor.iml @@ -2,8 +2,14 @@ - - + + + + + + + \ No newline at end of file diff --git a/.idea/inspectionProfiles/Project_Default.xml b/.idea/inspectionProfiles/Project_Default.xml index cce1d8640..ba941c14e 100644 --- a/.idea/inspectionProfiles/Project_Default.xml +++ b/.idea/inspectionProfiles/Project_Default.xml @@ -2,6 +2,13 @@ \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml index 09adcc7f2..4776a1dbc 100644 --- a/.idea/misc.xml +++ b/.idea/misc.xml @@ -1,5 +1,5 @@ - + \ No newline at end of file diff --git a/package-parser/README.md b/package-parser/README.md index 7c6223adb..2e1dcbecc 100644 --- a/package-parser/README.md +++ b/package-parser/README.md @@ -59,6 +59,19 @@ optional arguments: -m MIN, --min MIN Minimum number of usages required to keep an API element. ``` +### generate command + +```text +usage: parse-package generate [-h] -a API -u USAGES -o OUT + +optional arguments: + -h, --help show this help message and exit + -a API, --api API File created by the 'api' command. + -u USAGES, --usages USAGES + File created by the 'usages' command. + -o OUT, --out OUT Output directory. +``` + ### Example usage 1. Install Python 3.9. diff --git a/package-parser/package-parser.iml b/package-parser/package-parser.iml index 0763b9b57..4c2e3eaae 100644 --- a/package-parser/package-parser.iml +++ b/package-parser/package-parser.iml @@ -8,7 +8,13 @@ - + - + + + + + \ No newline at end of file diff --git a/package-parser/package_parser/cli.py b/package-parser/package_parser/cli.py index c82426cf3..8b5b50434 100644 --- a/package-parser/package_parser/cli.py +++ b/package-parser/package_parser/cli.py @@ -5,6 +5,7 @@ from typing import Any from .commands.find_usages import find_usages +from .commands.generate_annotations.generate_annotations import generate_annotations from .commands.get_api import distribution, distribution_version, get_api from .commands.get_dependencies import get_dependencies from .commands.suggest_improvements import suggest_improvements @@ -13,6 +14,7 @@ __API_COMMAND = "api" __USAGES_COMMAND = "usages" __IMPROVE_COMMAND = "improve" +__GENERATE_COMMAND = "generate" class CustomEncoder(json.JSONEncoder): @@ -55,6 +57,9 @@ def cli() -> None: elif args.command == __IMPROVE_COMMAND: suggest_improvements(args.api, args.usages, args.out, args.min) + elif args.command == __GENERATE_COMMAND: + generate_annotations(args.api, args.usages, args.out) + def __get_args() -> argparse.Namespace: parser = argparse.ArgumentParser(description="Analyze Python code.") @@ -64,6 +69,7 @@ def __get_args() -> argparse.Namespace: __add_api_subparser(subparsers) __add_usages_subparser(subparsers) __add_improve_subparser(subparsers) + __add_generate_subparser(subparsers) return parser.parse_args() @@ -141,3 +147,26 @@ def __add_improve_subparser(subparsers: _SubParsersAction) -> None: required=False, default=1, ) + + +def __add_generate_subparser(subparsers): + generate_parser = subparsers.add_parser( + __GENERATE_COMMAND, help="Generate Annotations automatically." + ) + generate_parser.add_argument( + "-a", + "--api", + help="File created by the 'api' command.", + type=argparse.FileType("r"), + required=True, + ) + generate_parser.add_argument( + "-u", + "--usages", + help="File created by the 'usages' command.", + type=argparse.FileType("r"), + required=True, + ) + generate_parser.add_argument( + "-o", "--out", help="Output directory.", type=Path, required=True + ) diff --git a/package-parser/package_parser/commands/generate_annotations/__init__.py b/package-parser/package_parser/commands/generate_annotations/__init__.py index 01f2fc13b..e69de29bb 100644 --- a/package-parser/package_parser/commands/generate_annotations/__init__.py +++ b/package-parser/package_parser/commands/generate_annotations/__init__.py @@ -1,2 +0,0 @@ -from ._combine import write_json -from ._generate_annotations import generate_annotations diff --git a/package-parser/package_parser/commands/generate_annotations/_combine.py b/package-parser/package_parser/commands/generate_annotations/_combine.py deleted file mode 100644 index cdc40e5b1..000000000 --- a/package-parser/package_parser/commands/generate_annotations/_combine.py +++ /dev/null @@ -1,102 +0,0 @@ -import argparse -import json -import os - - -def write_json(output_path: str, constant_path: str, unused_path: str) -> None: - """ - Dient zum Mergen von Unused-Dictionary und Constant-Dictionary und anschließende Erzeugen einer JSON - File, - die das erzeugte Dictionary beinhaltet. - - :param output_path: Dateipfad für Output-JSON aus beiden Dictionaries(Unused und Constant) - :param constant_path: Dateipfad zur Constant Annotation File, die eine Constant Dictionary enthält - :param unused_path: Dateipfad zur Unused Annotation File, die eine Unused Dictionary enthält - """ - # Platzhalter für echte Funktion - # unused_dict = find_unused(unused_path) - - # entfernen, wenn Funktion aus Issue 433 fertig - unused_dict = { - "sklearn/sklearn.__check_build/raise_build_error": { - "target": "sklearn/sklearn.__check_build/raise_build_error" - } - } - # Platzhalter für echte Funktion - # constant_dict = find_constant(constant_path) - - # entfernen, wenn Funktion aus Issue 434 fertig - constant_dict = { - "sklearn/sklearn._config/config_context/assume_finite": { - "target": "sklearn/sklearn._config/config_context/assume_finite", - "defaultType": "boolean", - "defaultValue": True, - }, - "sklearn/sklearn._config/config_context/working_memory": { - "target": "sklearn/sklearn._config/config_context/working_memory", - "defaultType": "string", - "defaultValue": "bla", - }, - "sklearn/sklearn._config/config_context/print_changed_only": { - "target": "sklearn/sklearn._config/config_context/print_changed_only", - "defaultType": "none", - "defaultValue": None, - }, - "sklearn/sklearn._config/config_context/display": { - "target": "sklearn/sklearn._config/config_context/display", - "defaultType": "number", - "defaultValue": "3", - }, - } - result_dict = __combine_dictionaries(unused_dict, constant_dict) - - with open(os.path.join(output_path, "annotations.json"), "w") as file: - json.dump( - result_dict, - file, - indent=2, - ) - - -def __combine_dictionaries(unused_dict: dict, constant_dict: dict) -> dict: - """ - Funktion, die die Dictionaries kombiniert - :param unused_dict : Dictionary der unused annotations - :param constant_dict : Dictionary der constant annotations - :return result_dict : Kombiniertes Dictionary - """ - - result_dict = { - "unused": unused_dict, - "constant": constant_dict, - } - return result_dict - - -# sollte, sobald final eingebunden wird entfernt werden, da es nicht weiter benötigt wird, dient zZ. nur zur Anschauung -if __name__ == "__main__": - parser = argparse.ArgumentParser(prog="combine") - - # Argument 1: outputPath - parser.add_argument( - "outputPath", - metavar="output-filepath", - type=str, - help="paste the location of the output file in here ", - ) - # Argument 2: inputPath(Pfad einer Constant-Datei) - parser.add_argument( - "constantPath", - metavar="input-filepath", - type=str, - help='paste the location of the "constant" file in here ', - ) - # Argument 3: inputPath(Pfad einer Unused-Datei) - parser.add_argument( - "unusedPath", - metavar="input-filepath", - type=str, - help='paste the location of the "unused" file in here ', - ) - # Erzeuge kombinierte JSON-Datei jeweils aus der Constant- und der Unused-Dictionary - args = parser.parse_args() # Argumente werden auf Nutzen als Parameter vorbereitet - write_json(args.outputPath, args.constantPath, args.unusedPath) diff --git a/package-parser/package_parser/commands/generate_annotations/_generate_unused_annotations.py b/package-parser/package_parser/commands/generate_annotations/_generate_unused_annotations.py deleted file mode 100644 index 59b40a90d..000000000 --- a/package-parser/package_parser/commands/generate_annotations/_generate_unused_annotations.py +++ /dev/null @@ -1,44 +0,0 @@ -import json -import re -from typing import Dict, List, Tuple - - -def generate_unused_annotations(in_file_path: str): - """ - Returns a Dict of unused functions or classes - - :param in_file_path: JSON file that contains a list of unused functions or classes - """ - - with open(in_file_path, "r", encoding="UTF-8") as in_file: - data = json.load(in_file) - - unuseds: Dict[str, Dict[str, str]] = {} - for name in data: - formatted_name = format_name(name) - unuseds[formatted_name] = {"target": formatted_name} - - return unuseds - - -def format_name(name: str): - if name is None: - return None - - parts = re.split("\\.", name) - newname = "sklearn/" + parts[0] - - if len(parts) == 1: - return newname - - slash = False - for part in parts[1:-1]: - if not slash and re.match("^_{0,2}[A-Z]", part): - slash = True - if slash: - newname += "/" + part - else: - newname += "." + part - - newname += "/" + parts[-1] - return newname diff --git a/package-parser/package_parser/commands/generate_annotations/_generate_annotations.py b/package-parser/package_parser/commands/generate_annotations/generate_annotations.py similarity index 62% rename from package-parser/package_parser/commands/generate_annotations/_generate_annotations.py rename to package-parser/package_parser/commands/generate_annotations/generate_annotations.py index 3a2ee1f39..7f97cb513 100644 --- a/package-parser/package_parser/commands/generate_annotations/_generate_annotations.py +++ b/package-parser/package_parser/commands/generate_annotations/generate_annotations.py @@ -1,21 +1,27 @@ import json from io import TextIOWrapper from pathlib import Path -from typing import Any - -from package_parser.commands.find_usages import ( - ClassUsage, - FunctionUsage, - UsageStore, - ValueUsage, -) +from typing import Callable + +from package_parser.commands.find_usages import UsageStore from package_parser.commands.get_api import API from package_parser.utils import parent_qname def generate_annotations( - api_file: TextIOWrapper, usages_file: TextIOWrapper, out_dir: Path -): + api_file: TextIOWrapper, usages_file: TextIOWrapper, output_file: Path +) -> None: + """ + Generates an annotation file from the given API and UsageStore files, and writes it to the given output file. + Annotations that are generated are: constant, unused, + :param api_file: API file + :param usages_file: UsageStore file + :param output_file: Output file + :return: None + """ + if api_file is None or usages_file is None or output_file is None: + raise ValueError("api_file, usages_file, and output_file must be specified.") + with api_file: api_json = json.load(api_file) api = API.from_json(api_json) @@ -24,15 +30,143 @@ def generate_annotations( usages_json = json.load(usages_file) usages = UsageStore.from_json(usages_json) - # out_dir.mkdir(parents=True, exist_ok=True) - # base_file_name = api_file.name.replace("__api.json", "") + annotation_functions = [__get_unused_annotations, __get_constant_annotations] + + annotations_dict = __generate_annotation_dict(api, usages, annotation_functions) + + with output_file.open("w") as f: + json.dump(annotations_dict, f, indent=2) + + +def __generate_annotation_dict(api: API, usages: UsageStore, functions: list[Callable]): + _preprocess_usages(usages, api) + + annotations_dict: dict[str, dict[str, dict[str, str]]] = {} + for generate_annotation in functions: + annotations_dict.update(generate_annotation(usages, api)) + + return annotations_dict + + +def __get_constant_annotations( + usages: UsageStore, api: API +) -> dict[str, dict[str, dict[str, str]]]: + """ + Returns all parameters that are only ever assigned a single value. + :param usages: UsageStore object + :param api: API object for usages + :return: {"constant": dict[str, dict[str, str]]} + """ + constant = "constant" + constants: dict[str, dict[str, str]] = {} + + for parameter_qname in list(usages.parameter_usages.keys()): + if len(usages.value_usages[parameter_qname].values()) == 0: + continue + + if len(usages.value_usages[parameter_qname].keys()) == 1: + if usages.most_common_value(parameter_qname) is None: + continue + + target_name = __qname_to_target_name(api, parameter_qname) + default_type, default_value = __get_default_type_from_value( + str(usages.most_common_value(parameter_qname)) + ) + + constants[target_name] = { + "target": target_name, + "defaultType": default_type, + "defaultValue": default_value, + } + + return {constant: constants} + + +def __get_unused_annotations( + usages: UsageStore, api: API +) -> dict[str, dict[str, dict[str, str]]]: + """ + Returns all parameters that are never used. + :param usages: UsageStore object + :param api: API object for usages + :return: {"unused": dict[str, dict[str, str]]} + """ + unused = "unused" + unuseds: dict[str, dict[str, str]] = {} + + for parameter_name in list(api.parameters().keys()): + if ( + parameter_name not in usages.parameter_usages + or len(usages.parameter_usages[parameter_name]) == 0 + ): + formatted_name = __qname_to_target_name(api, parameter_name) + unuseds[formatted_name] = {"target": formatted_name} + + for function_name in list(api.functions.keys()): + if ( + function_name not in usages.function_usages + or len(usages.function_usages[function_name]) == 0 + ): + formatted_name = __qname_to_target_name(api, function_name) + unuseds[formatted_name] = {"target": formatted_name} + + for class_name in list(api.classes.keys()): + if ( + class_name not in usages.class_usages + or len(usages.class_usages[class_name]) == 0 + ): + formatted_name = __qname_to_target_name(api, class_name) + unuseds[formatted_name] = {"target": formatted_name} + + return {unused: unuseds} + + +def __qname_to_target_name(api: API, qname: str) -> str: + """ + Formats the given name to the wanted format. This method is to be removed as soon as the UsageStore is updated to + use the new format. + :param api: API object + :param qname: Name pre-formatting + :return: Formatted name + """ + if qname is None or api is None: + raise ValueError("qname and api must be specified.") + + target_elements = qname.split(".") + + package_name = api.package + module_name = class_name = function_name = parameter_name = "" + + if ".".join(target_elements) in api.parameters().keys(): + parameter_name = "/" + target_elements.pop() + if ".".join(target_elements) in api.functions.keys(): + function_name = f"/{target_elements.pop()}" + if ".".join(target_elements) in api.classes.keys(): + class_name = f"/{target_elements.pop()}" + if ".".join(target_elements) in api.modules.keys(): + module_name = "/" + ".".join(target_elements) + + return package_name + module_name + class_name + function_name + parameter_name + + +def __get_default_type_from_value(default_value: str) -> tuple[str, str]: + default_value = str(default_value)[1:-1] + + if default_value == "null": + default_type = "none" + elif default_value == "True" or default_value == "False": + default_type = "boolean" + elif default_value.isnumeric(): + default_type = "number" + default_value = default_value + else: + default_type = "string" + default_value = default_value - __preprocess_usages(usages, api) - constant_parameters = __find_constant_parameters(usages, api) - return constant_parameters + return default_type, default_value -def __preprocess_usages(usages: UsageStore, api: API) -> None: +def _preprocess_usages(usages: UsageStore, api: API) -> None: __remove_internal_usages(usages, api) __add_unused_api_elements(usages, api) __add_implicit_usages_of_default_value(usages, api) @@ -121,70 +255,3 @@ def __add_implicit_usages_of_default_value(usages: UsageStore, api: API) -> None for location in locations_of_implicit_usages_of_default_value: usages.add_value_usage(parameter_qname, default_value, location) - - -def __find_constant_parameters( - usages: UsageStore, api: API -) -> dict[str, dict[str, str]]: - """ - Returns all parameters that are only ever assigned a single value. - - :param usages: Usage store - """ - - result = {} - - for parameter_qname in list(usages.parameter_usages.keys()): - - if len(usages.value_usages[parameter_qname].values()) == 0: - continue - - if len(usages.value_usages[parameter_qname].keys()) == 1: - target_name = __qname_to_target_name(api, parameter_qname) - default_type, default_value = __get_default_type_from_value( - str(usages.most_common_value(parameter_qname)) - ) - print(target_name) - result[target_name] = { - "target": target_name, - "defaultType": default_type, - "defaultValue": default_value, - } - - print(json.dumps(result)) - return result - - -def __qname_to_target_name(api: API, qname: str) -> str: - target_elements = qname.split(".") - - package_name = api.package - module_name = class_name = function_name = parameter_name = "" - - if ".".join(target_elements) in api.parameters().keys(): - parameter_name = "/" + target_elements.pop() - if ".".join(target_elements) in api.functions.keys(): - function_name = f"/{target_elements.pop()}" - if ".".join(target_elements) in api.classes.keys(): - class_name = f"/{target_elements.pop()}" - if ".".join(target_elements) in api.modules.keys(): - module_name = "/" + ".".join(target_elements) - - return package_name + module_name + class_name + function_name + parameter_name - - -def __get_default_type_from_value(default_value: str) -> tuple[str, str]: - default_value = str(default_value)[1:-1] - - if default_value == "null": - default_type = "none" - elif default_value == "True" or default_value == "False": - default_type = "boolean" - elif default_value.isnumeric(): - default_type = "number" - default_value = default_value - else: - default_type = "string" - default_value = default_value - - return default_type, default_value diff --git a/package-parser/tests/commands/generate_annotations/annotations.json b/package-parser/tests/commands/generate_annotations/annotations.json deleted file mode 100644 index 5e53ce458..000000000 --- a/package-parser/tests/commands/generate_annotations/annotations.json +++ /dev/null @@ -1,29 +0,0 @@ -{ - "unused": { - "sklearn/sklearn.__check_build/raise_build_error": { - "target": "sklearn/sklearn.__check_build/raise_build_error" - } - }, - "constant": { - "sklearn/sklearn._config/config_context/assume_finite": { - "target": "sklearn/sklearn._config/config_context/assume_finite", - "defaultType": "boolean", - "defaultValue": true - }, - "sklearn/sklearn._config/config_context/working_memory": { - "target": "sklearn/sklearn._config/config_context/working_memory", - "defaultType": "string", - "defaultValue": "bla" - }, - "sklearn/sklearn._config/config_context/print_changed_only": { - "target": "sklearn/sklearn._config/config_context/print_changed_only", - "defaultType": "none", - "defaultValue": null - }, - "sklearn/sklearn._config/config_context/display": { - "target": "sklearn/sklearn._config/config_context/display", - "defaultType": "number", - "defaultValue": "3" - } - } -} diff --git a/package-parser/tests/commands/generate_annotations/test_combine.py b/package-parser/tests/commands/generate_annotations/test_combine.py deleted file mode 100644 index 45c901b0f..000000000 --- a/package-parser/tests/commands/generate_annotations/test_combine.py +++ /dev/null @@ -1,82 +0,0 @@ -import json -import os - -import package_parser.commands.generate_annotations._combine as comb -import pytest - -test_unused_dict = { - "sklearn/sklearn.__check_build/raise_build_error": { - "target": "sklearn/sklearn.__check_build/raise_build_error" - } -} - -test_constant_dict = { - "sklearn/sklearn._config/config_context/assume_finite": { - "target": "sklearn/sklearn._config/config_context/assume_finite", - "defaultType": "boolean", - "defaultValue": True, - }, - "sklearn/sklearn._config/config_context/working_memory": { - "target": "sklearn/sklearn._config/config_context/working_memory", - "defaultType": "string", - "defaultValue": "bla", - }, - "sklearn/sklearn._config/config_context/print_changed_only": { - "target": "sklearn/sklearn._config/config_context/print_changed_only", - "defaultType": "none", - "defaultValue": None, - }, - "sklearn/sklearn._config/config_context/display": { - "target": "sklearn/sklearn._config/config_context/display", - "defaultType": "number", - "defaultValue": "3", - }, -} - -test_combined_dict = { - "unused": { - "sklearn/sklearn.__check_build/raise_build_error": { - "target": "sklearn/sklearn.__check_build/raise_build_error" - } - }, - "constant": { - "sklearn/sklearn._config/config_context/assume_finite": { - "target": "sklearn/sklearn._config/config_context/assume_finite", - "defaultType": "boolean", - "defaultValue": True, - }, - "sklearn/sklearn._config/config_context/working_memory": { - "target": "sklearn/sklearn._config/config_context/working_memory", - "defaultType": "string", - "defaultValue": "bla", - }, - "sklearn/sklearn._config/config_context/print_changed_only": { - "target": "sklearn/sklearn._config/config_context/print_changed_only", - "defaultType": "none", - "defaultValue": None, - }, - "sklearn/sklearn._config/config_context/display": { - "target": "sklearn/sklearn._config/config_context/display", - "defaultType": "number", - "defaultValue": "3", - }, - }, -} - - -@pytest.mark.parametrize( - "test_unused, test_constant, expected", - [ - (test_unused_dict, test_constant_dict, test_combined_dict), - ], -) -def test_combine_dictionaries(test_unused, test_constant, expected): - """ - Funktion, die feststellt ob die kombinierte JSON-Datei gleich der gefragten JSON-Datei ist oder nicht - :param test_unused: Unused Dictionary als Parameter - :param test_constant: Constant Dictionary als Parameter - :param expected: gemergte JSON-Datei aus den 2 Dictionaries, die entsteht - """ - - eval_dict = comb.__combine_dictionaries(test_unused, test_constant) - assert eval_dict == expected diff --git a/package-parser/tests/commands/generate_annotations/test_determine_constant_parameters.py b/package-parser/tests/commands/generate_annotations/test_determine_constant_parameters.py deleted file mode 100644 index 2c9807114..000000000 --- a/package-parser/tests/commands/generate_annotations/test_determine_constant_parameters.py +++ /dev/null @@ -1,59 +0,0 @@ -import json -import os - -import pytest -from package_parser.commands.find_usages._model import UsageStore -from package_parser.commands.generate_annotations._generate_annotations import ( - generate_annotations, -) - -# Expected output: -# @Unused annotations should be created for the following declarations: -# -# test.Unused_Class -# test.unused_global_function -# test.Commonly_Used_Class.unused_method -# -# @Constant annotations should be created for the following parameters: -# -# test.commonly_used_global_function.useless_required_parameter (with value "'blup'") -# test.commonly_used_global_function.unused_optional_parameter (with value "'bla'", i.e. the default value) -# test.commonly_used_global_function.useless_optional_parameter (with value "'bla'") - - -def test_determination_of_constant_parameters(): - - expected = { - "test/test/commonly_used_global_function/useless_required_parameter": { - "target": "test/test/commonly_used_global_function/useless_required_parameter", - "defaultType": "string", - "defaultValue": "blup", - }, - "test/test/commonly_used_global_function/unused_optional_parameter": { - "target": "test/test/commonly_used_global_function/unused_optional_parameter", - "defaultType": "string", - "defaultValue": "bla", - }, - "test/test/commonly_used_global_function/useless_optional_parameter": { - "target": "test/test/commonly_used_global_function/useless_optional_parameter", - "defaultType": "string", - "defaultValue": "bla", - }, - } - - api_json_path = os.path.join( - os.getcwd(), "tests", "data", "constant", "api_data.json" - ) - usages_json_path = os.path.join( - os.getcwd(), "tests", "data", "constant", "usage_data.json" - ) - - api_file = open(api_json_path) - usages_file = open(usages_json_path) - - constant_parameters = generate_annotations(api_file, usages_file, "/.") - - api_file.close() - usages_file.close() - - assert constant_parameters == expected diff --git a/package-parser/tests/commands/generate_annotations/test_generate_annotations.py b/package-parser/tests/commands/generate_annotations/test_generate_annotations.py new file mode 100644 index 000000000..82324f31b --- /dev/null +++ b/package-parser/tests/commands/generate_annotations/test_generate_annotations.py @@ -0,0 +1,134 @@ +import json +import os +from pathlib import Path + +import pytest +from package_parser.commands.find_usages import UsageStore +from package_parser.commands.generate_annotations.generate_annotations import ( + __get_constant_annotations, + __get_unused_annotations, + __qname_to_target_name, + _preprocess_usages, + generate_annotations, +) +from package_parser.commands.get_api import API + +UNUSED_EXPECTED = { + "unused": { + "test/test/Unused_Class": {"target": "test/test/Unused_Class"}, + "test/test/commonly_used_global_function/unused_optional_parameter": { + "target": "test/test/commonly_used_global_function/unused_optional_parameter" + }, + "test/test/unused_global_function": { + "target": "test/test/unused_global_function" + }, + "test/test/unused_global_function/unused_optional_parameter": { + "target": "test/test/unused_global_function/unused_optional_parameter" + }, + "test/test/unused_global_function/unused_required_parameter": { + "target": "test/test/unused_global_function/unused_required_parameter" + }, + } +} + +CONSTANT_EXPECTED = { + "constant": { + "test/test/commonly_used_global_function/unused_optional_parameter": { + "defaultType": "string", + "defaultValue": "bla", + "target": "test/test/commonly_used_global_function/unused_optional_parameter", + }, + "test/test/commonly_used_global_function/useless_optional_parameter": { + "defaultType": "string", + "defaultValue": "bla", + "target": "test/test/commonly_used_global_function/useless_optional_parameter", + }, + "test/test/commonly_used_global_function/useless_required_parameter": { + "defaultType": "string", + "defaultValue": "blup", + "target": "test/test/commonly_used_global_function/useless_required_parameter", + }, + } +} + +# Reihenfolge ist wichtig, siehe Reihenfolge von annotation_functions in generate_annotations.py +FULL_EXPECTED = {**UNUSED_EXPECTED, **CONSTANT_EXPECTED} + + +def setup(): + api_json_path = os.path.join(os.getcwd(), "tests", "data", "api_data.json") + usages_json_path = os.path.join(os.getcwd(), "tests", "data", "usage_data.json") + + with open(api_json_path, "r") as api_file: + api_json = json.load(api_file) + api = API.from_json(api_json) + + with open(usages_json_path, "r") as usages_file: + usages_json = json.load(usages_file) + usages = UsageStore.from_json(usages_json) + + return usages, api, usages_file, api_file, usages_json_path, api_json_path + + +def test_format_function(): + usages, api, usages_file, api_file, usages_json_path, api_json_path = setup() + assert ( + __qname_to_target_name(api, "test.unused_global_function") + == "test/test/unused_global_function" + ) + + +def test_format_parameter(): + usages, api, usages_file, api_file, usages_json_path, api_json_path = setup() + assert ( + __qname_to_target_name( + api, "test.commonly_used_global_function.useless_required_parameter" + ) + == "test/test/commonly_used_global_function/useless_required_parameter" + ) + + +def test_format_none(): + usages, api, usages_file, api_file, usages_json_path, api_json_path = setup() + with pytest.raises(ValueError): + __qname_to_target_name(None, "test") + with pytest.raises(ValueError): + __qname_to_target_name(api, None) + + +def test_get_unused(): + usages, api, usages_file, api_file, usages_json_path, api_json_path = setup() + _preprocess_usages(usages, api) + assert __get_unused_annotations(usages, api) == UNUSED_EXPECTED + + +def test_get_constant(): + usages, api, usages_file, api_file, usages_json_path, api_json_path = setup() + _preprocess_usages(usages, api) + assert __get_constant_annotations(usages, api) == CONSTANT_EXPECTED + + +def test_generate(): + usages, api, usages_file, api_file, usages_json_path, api_json_path = setup() + out_file_path = os.path.join( + os.getcwd(), "tests", "out", "test_generate_out_file.json" + ) + + if not os.path.exists(os.path.join(os.getcwd(), "tests", "out")): + os.makedirs(os.path.join(os.getcwd(), "tests", "out")) + + if not os.path.exists(out_file_path) or not os.path.isfile(out_file_path): + with open(out_file_path, "x") as out_file: + out_file.write("") + + generate_annotations( + open(api_json_path, "r"), open(usages_json_path, "r"), Path(out_file_path) + ) + with open(out_file_path, "r") as out_file: + out_json = json.load(out_file) + assert out_json == FULL_EXPECTED + + +def test_generate_bad_path(): + with pytest.raises(ValueError): + generate_annotations(None, None, None) diff --git a/package-parser/tests/commands/generate_annotations/test_generate_unused_annotations.py b/package-parser/tests/commands/generate_annotations/test_generate_unused_annotations.py deleted file mode 100644 index 1c6a74ae6..000000000 --- a/package-parser/tests/commands/generate_annotations/test_generate_unused_annotations.py +++ /dev/null @@ -1,67 +0,0 @@ -import pytest -from package_parser.commands.generate_annotations._generate_unused_annotations import ( - format_name, - generate_unused_annotations, -) - -EXPECTED_VALUE = { - "sklearn/sklearn.base/_BaseEstimator/__setstate__": { - "target": "sklearn/sklearn.base/_BaseEstimator/__setstate__" - }, - "sklearn/sklearn.base/is_regressor": { - "target": "sklearn/sklearn.base/is_regressor" - }, - "sklearn/sklearn.cluster._agglomerative/linkage_tree": { - "target": "sklearn/sklearn.cluster._agglomerative/linkage_tree" - }, - "sklearn/sklearn.cluster._kmeans/MiniBatchKMeans/init_size_": { - "target": "sklearn/sklearn.cluster._kmeans/MiniBatchKMeans/init_size_" - }, -} - - -def test_format_underscores(): - assert ( - format_name("sklearn.cluster._kmeans._MiniBatchKMeans.random_state_") - == "sklearn/sklearn.cluster._kmeans/_MiniBatchKMeans/random_state_" - ) - - -def test_format_uppercase(): - assert ( - format_name("sklearn.cluster._kmeans.MiniBatchKMeans.random_state_") - == "sklearn/sklearn.cluster._kmeans/MiniBatchKMeans/random_state_" - ) - - -def test_format_normal(): - assert ( - format_name("sklearn.cluster._mean_shift.get_bin_seeds") - == "sklearn/sklearn.cluster._mean_shift/get_bin_seeds" - ) - - -def test_format_one_part(): - assert format_name("test") == "sklearn/test" - - -def test_format_none(): - assert format_name(None) is None - - -def test_format_empty(): - assert format_name("") == "sklearn/" - - -def test_generate(): - assert ( - generate_unused_annotations( - "tests/commands/generate_annotations/unused_functions_list.json" - ) - == EXPECTED_VALUE - ) - - -def test_generate_bad_path(): - with pytest.raises(FileNotFoundError): - generate_unused_annotations("aaaaaaaaaaaAAAAAAAAAAAA") diff --git a/package-parser/tests/commands/generate_annotations/unused_functions_list.json b/package-parser/tests/commands/generate_annotations/unused_functions_list.json deleted file mode 100644 index e4e850adc..000000000 --- a/package-parser/tests/commands/generate_annotations/unused_functions_list.json +++ /dev/null @@ -1,6 +0,0 @@ -[ - "sklearn.base._BaseEstimator.__setstate__", - "sklearn.base.is_regressor", - "sklearn.cluster._agglomerative.linkage_tree", - "sklearn.cluster._kmeans.MiniBatchKMeans.init_size_" -] diff --git a/package-parser/tests/data/constant/api_data.json b/package-parser/tests/data/api_data.json similarity index 72% rename from package-parser/tests/data/constant/api_data.json rename to package-parser/tests/data/api_data.json index d4bca8ec1..8c09e7e42 100644 --- a/package-parser/tests/data/constant/api_data.json +++ b/package-parser/tests/data/api_data.json @@ -7,14 +7,57 @@ "name": "test", "imports": [], "from_imports": [], - "classes": [], + "classes": [ + "test.Unused_Class", + "test.Rarely_Used_Class", + "test.Commonly_Used_Class" + ], "functions": [ "test.unused_global_function", + "test.rarely_used_global_function", "test.commonly_used_global_function" ] } ], - "classes": [], + "classes": [ + { + "name": "Unused_Class", + "qname": "test.Unused_Class", + "decorators": [], + "is_public": true, + "description": "", + "docstring": "", + "superclasses": [], + "source_code": "", + "methods": [] + }, + { + "name": "Rarely_Used_Class", + "qname": "test.Rarely_Used_Class", + "decorators": [], + "is_public": true, + "description": "This class is used rarely", + "docstring": "This class is used rarely", + "superclasses": [], + "source_code": "", + "methods": [] + }, + { + "name": "Commonly_Used_Class", + "qname": "test.Commonly_Used_Class", + "decorators": [], + "is_public": true, + "description": "This class is used commonly", + "docstring": "This class is used commonly", + "superclasses": [], + "source_code": "", + "methods": [ + "test.Commonly_Used_Class.unused_method", + "test.Commonly_Used_Class.rarely_used_method", + "test.Commonly_Used_Class.commonly_used_method" + ] + } + ], "functions": [ { "name": "unused_global_function", diff --git a/package-parser/tests/data/unused/api_data.json b/package-parser/tests/data/unused/api_data.json deleted file mode 100644 index 3164ee5fb..000000000 --- a/package-parser/tests/data/unused/api_data.json +++ /dev/null @@ -1,129 +0,0 @@ -{ - "distribution": "test", - "package": "test", - "version": "0.0.1", - "modules": [ - { - "name": "test", - "imports": [], - "from_imports": [], - "classes": [ - "test.Unused_Class", - "test.Rarely_Used_Class", - "test.Commonly_Used_Class" - ], - "functions": [ - "test.unused_global_function", - "test.rarely_used_global_function", - "test.commonly_used_global_function" - ] - } - ], - "classes": [ - { - "name": "Unused_Class", - "qname": "test.Unused_Class", - "decorators": [], - "superclasses": [], - "methods": [] - }, - { - "name": "Rarely_Used_Class", - "qname": "test.Rarely_Used_Class", - "decorators": [], - "superclasses": [], - "methods": [] - }, - { - "name": "Commonly_Used_Class", - "qname": "test.Commonly_Used_Class", - "decorators": [], - "superclasses": [], - "methods": [ - "test.Commonly_Used_Class.unused_method", - "test.Commonly_Used_Class.rarely_used_method", - "test.Commonly_Used_Class.commonly_used_method" - ] - } - ], - "functions": [ - { - "name": "unused_global_function", - "unique_name": "unused_global_function", - "qname": "test.unused_global_function", - "unique_qname": "test.unused_global_function", - "decorators": [], - "parameters": [], - "results": [], - "is_public": true, - "description": "", - "docstring": "", - "source_code": "" - }, - { - "name": "rarely_used_global_function", - "unique_name": "rarely_used_global_function", - "qname": "test.rarely_used_global_function", - "unique_qname": "test.rarely_used_global_function", - "decorators": [], - "parameters": [], - "results": [], - "is_public": true, - "description": "", - "docstring": "", - "source_code": "" - }, - { - "name": "commonly_used_global_function", - "unique_name": "commonly_used_global_function", - "qname": "test.commonly_used_global_function", - "unique_qname": "test.commonly_used_global_function", - "decorators": [], - "parameters": [], - "results": [], - "is_public": true, - "description": "", - "docstring": "", - "source_code": "" - }, - { - "name": "unused_method", - "unique_name": "unused_method", - "qname": "test.Commonly_Used_Class.unused_method", - "unique_qname": "test.Commonly_Used_Class.unused_method", - "decorators": [], - "parameters": [], - "results": [], - "is_public": true, - "description": "", - "docstring": "", - "source_code": "" - }, - { - "name": "rarely_used_method", - "unique_name": "rarely_used_method", - "qname": "test.Commonly_Used_Class.rarely_used_method", - "unique_qname": "test.Commonly_Used_Class.rarely_used_method", - "decorators": [], - "parameters": [], - "results": [], - "is_public": true, - "description": "", - "docstring": "", - "source_code": "" - }, - { - "name": "commonly_used_method", - "unique_name": "commonly_used_method", - "qname": "test.Commonly_Used_Class.commonly_used_method", - "unique_qname": "test.Commonly_Used_Class.commonly_used_method", - "decorators": [], - "parameters": [], - "results": [], - "is_public": true, - "description": "", - "docstring": "", - "source_code": "" - } - ] -} diff --git a/package-parser/tests/data/unused/usage_data.json b/package-parser/tests/data/unused/usage_data.json deleted file mode 100644 index 39a6d40c6..000000000 --- a/package-parser/tests/data/unused/usage_data.json +++ /dev/null @@ -1,113 +0,0 @@ -{ - "class_usages": { - "test.Unused_Class": [], - "test.Rarely_Used_Class": [ - { - "file": "test.py", - "line": 1, - "column": 1 - } - ], - "test.Commonly_Used_Class": [ - { - "file": "test.py", - "line": 1, - "column": 1 - }, - { - "file": "test.py", - "line": 1, - "column": 1 - }, - { - "file": "test.py", - "line": 1, - "column": 1 - }, - { - "file": "test.py", - "line": 1, - "column": 1 - }, - { - "file": "test.py", - "line": 1, - "column": 1 - } - ] - }, - "function_usages": { - "test.unused_global_function": [], - "test.rarely_used_global_function": [ - { - "file": "test.py", - "line": 1, - "column": 1 - } - ], - "test.commonly_used_global_function": [ - { - "file": "test.py", - "line": 1, - "column": 1 - }, - { - "file": "test.py", - "line": 1, - "column": 1 - }, - { - "file": "test.py", - "line": 1, - "column": 1 - }, - { - "file": "test.py", - "line": 1, - "column": 1 - }, - { - "file": "test.py", - "line": 1, - "column": 1 - } - ], - "test.Commonly_Used_Class.unused_method": [], - "test.Commonly_Used_Class.rarely_used_method": [ - { - "file": "test.py", - "line": 1, - "column": 1 - } - ], - "test.Commonly_Used_Class.commonly_used_method": [ - { - "file": "test.py", - "line": 1, - "column": 1 - }, - { - "file": "test.py", - "line": 1, - "column": 1 - }, - { - "file": "test.py", - "line": 1, - "column": 1 - }, - { - "file": "test.py", - "line": 1, - "column": 1 - }, - { - "file": "test.py", - "line": 1, - "column": 1 - } - ] - }, - "parameter_usages": {}, - "value_usages": {} -} diff --git a/package-parser/tests/data/constant/usage_data.json b/package-parser/tests/data/usage_data.json similarity index 94% rename from package-parser/tests/data/constant/usage_data.json rename to package-parser/tests/data/usage_data.json index 875c7df31..93d57be69 100644 --- a/package-parser/tests/data/constant/usage_data.json +++ b/package-parser/tests/data/usage_data.json @@ -1,5 +1,20 @@ { - "class_usages": {}, + "class_usages": { + "test.Commonly_Used_Class": [ + { + "file": "test.py", + "line": 1, + "column": 1 + } + ], + "test.Rarely_Used_Class": [ + { + "file": "test.py", + "line": 1, + "column": 1 + } + ] + }, "function_usages": { "test.unused_global_function": [], "test.commonly_used_global_function": [