CoolFace
Apppublic

chendl/compositional_test

sourceHugging Faceupdated 3y agoView on Hugging Face
1likes
check_inits.py306 linesDownload Raw Back to utils
1# coding=utf-82# Copyright 2020 The HuggingFace Inc. team.3#4# Licensed under the Apache License, Version 2.0 (the "License");5# you may not use this file except in compliance with the License.6# You may obtain a copy of the License at7#8#     http://www.apache.org/licenses/LICENSE-2.09#10# Unless required by applicable law or agreed to in writing, software11# distributed under the License is distributed on an "AS IS" BASIS,12# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.13# See the License for the specific language governing permissions and14# limitations under the License.15 16import collections17import os18import re19from pathlib import Path20 21 22PATH_TO_TRANSFORMERS = "src/transformers"23 24 25# Matches is_xxx_available()26_re_backend = re.compile(r"is\_([a-z_]*)_available()")27# Catches a one-line _import_struct = {xxx}28_re_one_line_import_struct = re.compile(r"^_import_structure\s+=\s+\{([^\}]+)\}")29# Catches a line with a key-values pattern: "bla": ["foo", "bar"]30_re_import_struct_key_value = re.compile(r'\s+"\S*":\s+\[([^\]]*)\]')31# Catches a line if not is_foo_available32_re_test_backend = re.compile(r"^\s*if\s+not\s+is\_[a-z_]*\_available\(\)")33# Catches a line _import_struct["bla"].append("foo")34_re_import_struct_add_one = re.compile(r'^\s*_import_structure\["\S*"\]\.append\("(\S*)"\)')35# Catches a line _import_struct["bla"].extend(["foo", "bar"]) or _import_struct["bla"] = ["foo", "bar"]36_re_import_struct_add_many = re.compile(r"^\s*_import_structure\[\S*\](?:\.extend\(|\s*=\s+)\[([^\]]*)\]")37# Catches a line with an object between quotes and a comma:     "MyModel",38_re_quote_object = re.compile('^\s+"([^"]+)",')39# Catches a line with objects between brackets only:    ["foo", "bar"],40_re_between_brackets = re.compile("^\s+\[([^\]]+)\]")41# Catches a line with from foo import bar, bla, boo42_re_import = re.compile(r"\s+from\s+\S*\s+import\s+([^\(\s].*)\n")43# Catches a line with try:44_re_try = re.compile(r"^\s*try:")45# Catches a line with else:46_re_else = re.compile(r"^\s*else:")47 48 49def find_backend(line):50    """Find one (or multiple) backend in a code line of the init."""51    if _re_test_backend.search(line) is None:52        return None53    backends = [b[0] for b in _re_backend.findall(line)]54    backends.sort()55    return "_and_".join(backends)56 57 58def parse_init(init_file):59    """60    Read an init_file and parse (per backend) the _import_structure objects defined and the TYPE_CHECKING objects61    defined62    """63    with open(init_file, "r", encoding="utf-8", newline="\n") as f:64        lines = f.readlines()65 66    line_index = 067    while line_index < len(lines) and not lines[line_index].startswith("_import_structure = {"):68        line_index += 169 70    # If this is a traditional init, just return.71    if line_index >= len(lines):72        return None73 74    # First grab the objects without a specific backend in _import_structure75    objects = []76    while not lines[line_index].startswith("if TYPE_CHECKING") and find_backend(lines[line_index]) is None:77        line = lines[line_index]78        # If we have everything on a single line, let's deal with it.79        if _re_one_line_import_struct.search(line):80            content = _re_one_line_import_struct.search(line).groups()[0]81            imports = re.findall("\[([^\]]+)\]", content)82            for imp in imports:83                objects.extend([obj[1:-1] for obj in imp.split(", ")])84            line_index += 185            continue86        single_line_import_search = _re_import_struct_key_value.search(line)87        if single_line_import_search is not None:88            imports = [obj[1:-1] for obj in single_line_import_search.groups()[0].split(", ") if len(obj) > 0]89            objects.extend(imports)90        elif line.startswith(" " * 8 + '"'):91            objects.append(line[9:-3])92        line_index += 193 94    import_dict_objects = {"none": objects}95    # Let's continue with backend-specific objects in _import_structure96    while not lines[line_index].startswith("if TYPE_CHECKING"):97        # If the line is an if not is_backend_available, we grab all objects associated.98        backend = find_backend(lines[line_index])99        # Check if the backend declaration is inside a try block:100        if _re_try.search(lines[line_index - 1]) is None:101            backend = None102 103        if backend is not None:104            line_index += 1105 106            # Scroll until we hit the else block of try-except-else107            while _re_else.search(lines[line_index]) is None:108                line_index += 1109 110            line_index += 1111 112            objects = []113            # Until we unindent, add backend objects to the list114            while len(lines[line_index]) <= 1 or lines[line_index].startswith(" " * 4):115                line = lines[line_index]116                if _re_import_struct_add_one.search(line) is not None:117                    objects.append(_re_import_struct_add_one.search(line).groups()[0])118                elif _re_import_struct_add_many.search(line) is not None:119                    imports = _re_import_struct_add_many.search(line).groups()[0].split(", ")120                    imports = [obj[1:-1] for obj in imports if len(obj) > 0]121                    objects.extend(imports)122                elif _re_between_brackets.search(line) is not None:123                    imports = _re_between_brackets.search(line).groups()[0].split(", ")124                    imports = [obj[1:-1] for obj in imports if len(obj) > 0]125                    objects.extend(imports)126                elif _re_quote_object.search(line) is not None:127                    objects.append(_re_quote_object.search(line).groups()[0])128                elif line.startswith(" " * 8 + '"'):129                    objects.append(line[9:-3])130                elif line.startswith(" " * 12 + '"'):131                    objects.append(line[13:-3])132                line_index += 1133 134            import_dict_objects[backend] = objects135        else:136            line_index += 1137 138    # At this stage we are in the TYPE_CHECKING part, first grab the objects without a specific backend139    objects = []140    while (141        line_index < len(lines)142        and find_backend(lines[line_index]) is None143        and not lines[line_index].startswith("else")144    ):145        line = lines[line_index]146        single_line_import_search = _re_import.search(line)147        if single_line_import_search is not None:148            objects.extend(single_line_import_search.groups()[0].split(", "))149        elif line.startswith(" " * 8):150            objects.append(line[8:-2])151        line_index += 1152 153    type_hint_objects = {"none": objects}154    # Let's continue with backend-specific objects155    while line_index < len(lines):156        # If the line is an if is_backend_available, we grab all objects associated.157        backend = find_backend(lines[line_index])158        # Check if the backend declaration is inside a try block:159        if _re_try.search(lines[line_index - 1]) is None:160            backend = None161 162        if backend is not None:163            line_index += 1164 165            # Scroll until we hit the else block of try-except-else166            while _re_else.search(lines[line_index]) is None:167                line_index += 1168 169            line_index += 1170 171            objects = []172            # Until we unindent, add backend objects to the list173            while len(lines[line_index]) <= 1 or lines[line_index].startswith(" " * 8):174                line = lines[line_index]175                single_line_import_search = _re_import.search(line)176                if single_line_import_search is not None:177                    objects.extend(single_line_import_search.groups()[0].split(", "))178                elif line.startswith(" " * 12):179                    objects.append(line[12:-2])180                line_index += 1181 182            type_hint_objects[backend] = objects183        else:184            line_index += 1185 186    return import_dict_objects, type_hint_objects187 188 189def analyze_results(import_dict_objects, type_hint_objects):190    """191    Analyze the differences between _import_structure objects and TYPE_CHECKING objects found in an init.192    """193 194    def find_duplicates(seq):195        return [k for k, v in collections.Counter(seq).items() if v > 1]196 197    if list(import_dict_objects.keys()) != list(type_hint_objects.keys()):198        return ["Both sides of the init do not have the same backends!"]199 200    errors = []201    for key in import_dict_objects.keys():202        duplicate_imports = find_duplicates(import_dict_objects[key])203        if duplicate_imports:204            errors.append(f"Duplicate _import_structure definitions for: {duplicate_imports}")205        duplicate_type_hints = find_duplicates(type_hint_objects[key])206        if duplicate_type_hints:207            errors.append(f"Duplicate TYPE_CHECKING objects for: {duplicate_type_hints}")208 209        if sorted(set(import_dict_objects[key])) != sorted(set(type_hint_objects[key])):210            name = "base imports" if key == "none" else f"{key} backend"211            errors.append(f"Differences for {name}:")212            for a in type_hint_objects[key]:213                if a not in import_dict_objects[key]:214                    errors.append(f"  {a} in TYPE_HINT but not in _import_structure.")215            for a in import_dict_objects[key]:216                if a not in type_hint_objects[key]:217                    errors.append(f"  {a} in _import_structure but not in TYPE_HINT.")218    return errors219 220 221def check_all_inits():222    """223    Check all inits in the transformers repo and raise an error if at least one does not define the same objects in224    both halves.225    """226    failures = []227    for root, _, files in os.walk(PATH_TO_TRANSFORMERS):228        if "__init__.py" in files:229            fname = os.path.join(root, "__init__.py")230            objects = parse_init(fname)231            if objects is not None:232                errors = analyze_results(*objects)233                if len(errors) > 0:234                    errors[0] = f"Problem in {fname}, both halves do not define the same objects.\n{errors[0]}"235                    failures.append("\n".join(errors))236    if len(failures) > 0:237        raise ValueError("\n\n".join(failures))238 239 240def get_transformers_submodules():241    """242    Returns the list of Transformers submodules.243    """244    submodules = []245    for path, directories, files in os.walk(PATH_TO_TRANSFORMERS):246        for folder in directories:247            # Ignore private modules248            if folder.startswith("_"):249                directories.remove(folder)250                continue251            # Ignore leftovers from branches (empty folders apart from pycache)252            if len(list((Path(path) / folder).glob("*.py"))) == 0:253                continue254            short_path = str((Path(path) / folder).relative_to(PATH_TO_TRANSFORMERS))255            submodule = short_path.replace(os.path.sep, ".")256            submodules.append(submodule)257        for fname in files:258            if fname == "__init__.py":259                continue260            short_path = str((Path(path) / fname).relative_to(PATH_TO_TRANSFORMERS))261            submodule = short_path.replace(".py", "").replace(os.path.sep, ".")262            if len(submodule.split(".")) == 1:263                submodules.append(submodule)264    return submodules265 266 267IGNORE_SUBMODULES = [268    "convert_pytorch_checkpoint_to_tf2",269    "modeling_flax_pytorch_utils",270    "models.esm.openfold_utils",271]272 273 274def check_submodules():275    # This is to make sure the transformers module imported is the one in the repo.276    from transformers.utils import direct_transformers_import277 278    transformers = direct_transformers_import(PATH_TO_TRANSFORMERS)279 280    import_structure_keys = set(transformers._import_structure.keys())281    # This contains all the base keys of the _import_structure object defined in the init, but if the user is missing282    # some optional dependencies, they may not have all of them. Thus we read the init to read all additions and283    # (potentiall re-) add them.284    with open(os.path.join(PATH_TO_TRANSFORMERS, "__init__.py"), "r") as f:285        init_content = f.read()286    import_structure_keys.update(set(re.findall(r"import_structure\[\"([^\"]*)\"\]", init_content)))287 288    module_not_registered = [289        module290        for module in get_transformers_submodules()291        if module not in IGNORE_SUBMODULES and module not in import_structure_keys292    ]293 294    if len(module_not_registered) > 0:295        list_of_modules = "\n".join(f"- {module}" for module in module_not_registered)296        raise ValueError(297            "The following submodules are not properly registed in the main init of Transformers:\n"298            f"{list_of_modules}\n"299            "Make sure they appear somewhere in the keys of `_import_structure` with an empty list as value."300        )301 302 303if __name__ == "__main__":304    check_all_inits()305    check_submodules()306