|  | 
|  | 1 | +from datetime import datetime | 
|  | 2 | +from time import sleep | 
|  | 3 | +from typing import Any | 
|  | 4 | + | 
|  | 5 | +from dateutil.parser import parse | 
|  | 6 | +from requests import ConnectionError, ConnectTimeout, post | 
|  | 7 | + | 
|  | 8 | +from app.config import settings | 
|  | 9 | +from app.utils import get_manager, parse_pip_constraints | 
|  | 10 | + | 
|  | 11 | +headers_github = { | 
|  | 12 | +    "Accept": "application/vnd.github.hawkgirl-preview+json", | 
|  | 13 | +    "Authorization": f"Bearer {settings.GITHUB_GRAPHQL_API_KEY}", | 
|  | 14 | +} | 
|  | 15 | + | 
|  | 16 | + | 
|  | 17 | +async def get_repo_data( | 
|  | 18 | +    owner: str, | 
|  | 19 | +    name: str, | 
|  | 20 | +    all_packages: dict[str, Any] | None = None, | 
|  | 21 | +    end_cursor: str | None = None, | 
|  | 22 | +) -> dict[str, Any]: | 
|  | 23 | +    if not all_packages: | 
|  | 24 | +        all_packages = {} | 
|  | 25 | +    if not end_cursor: | 
|  | 26 | +        query = f""" | 
|  | 27 | +        {{ | 
|  | 28 | +            repository(owner: "{owner}", name: "{name}") {{ | 
|  | 29 | +                dependencyGraphManifests {{ | 
|  | 30 | +                    nodes {{ | 
|  | 31 | +                        filename | 
|  | 32 | +                        dependencies {{ | 
|  | 33 | +                            pageInfo {{ | 
|  | 34 | +                                hasNextPage | 
|  | 35 | +                                endCursor | 
|  | 36 | +                            }} | 
|  | 37 | +                            nodes {{ | 
|  | 38 | +                                packageName | 
|  | 39 | +                                requirements | 
|  | 40 | +                            }} | 
|  | 41 | +                        }} | 
|  | 42 | +                    }} | 
|  | 43 | +                }} | 
|  | 44 | +            }} | 
|  | 45 | +        }} | 
|  | 46 | +        """ | 
|  | 47 | +    else: | 
|  | 48 | +        query = f""" | 
|  | 49 | +        {{ | 
|  | 50 | +            repository(owner: "{owner}", name: "{name}") {{ | 
|  | 51 | +                dependencyGraphManifests {{ | 
|  | 52 | +                    nodes {{ | 
|  | 53 | +                        filename | 
|  | 54 | +                        dependencies (after: "{end_cursor}") {{ | 
|  | 55 | +                            pageInfo {{ | 
|  | 56 | +                                hasNextPage | 
|  | 57 | +                                endCursor | 
|  | 58 | +                            }} | 
|  | 59 | +                            nodes {{ | 
|  | 60 | +                                packageName | 
|  | 61 | +                                requirements | 
|  | 62 | +                            }} | 
|  | 63 | +                        }} | 
|  | 64 | +                    }} | 
|  | 65 | +                }} | 
|  | 66 | +            }} | 
|  | 67 | +        }} | 
|  | 68 | +        """ | 
|  | 69 | +    while True: | 
|  | 70 | +        try: | 
|  | 71 | +            response = post( | 
|  | 72 | +                "https://api.github.com/graphql", | 
|  | 73 | +                json={"query": query}, | 
|  | 74 | +                headers=headers_github, | 
|  | 75 | +            ).json() | 
|  | 76 | +            break | 
|  | 77 | +        except (ConnectTimeout, ConnectionError): | 
|  | 78 | +            sleep(5) | 
|  | 79 | +    page_info, all_packages = await json_reader(response, all_packages) | 
|  | 80 | +    if not page_info["hasNextPage"]: | 
|  | 81 | +        return all_packages | 
|  | 82 | +    return await get_repo_data(owner, name, all_packages, page_info["endCursor"]) | 
|  | 83 | + | 
|  | 84 | + | 
|  | 85 | +async def json_reader( | 
|  | 86 | +    response: Any, all_packages: dict[str, Any] | 
|  | 87 | +) -> tuple[dict[str, Any], dict[str, Any]]: | 
|  | 88 | +    page_info = {"hasNextPage": None} | 
|  | 89 | +    for file_node in response["data"]["repository"]["dependencyGraphManifests"][ | 
|  | 90 | +        "nodes" | 
|  | 91 | +    ]: | 
|  | 92 | +        requirement_file_name = file_node["filename"] | 
|  | 93 | +        if requirement_file_name == "package-lock.json": | 
|  | 94 | +            continue | 
|  | 95 | +        page_info = file_node["dependencies"]["pageInfo"] | 
|  | 96 | +        requirement_file_manager = await get_manager(requirement_file_name) | 
|  | 97 | +        if not requirement_file_manager: | 
|  | 98 | +            continue | 
|  | 99 | +        if requirement_file_name not in all_packages: | 
|  | 100 | +            all_packages[requirement_file_name] = { | 
|  | 101 | +                "package_manager": requirement_file_manager, | 
|  | 102 | +                "dependencies": {}, | 
|  | 103 | +            } | 
|  | 104 | +        for depependency_node in file_node["dependencies"]["nodes"]: | 
|  | 105 | +            if requirement_file_manager == "MVN": | 
|  | 106 | +                if "=" in depependency_node["requirements"]: | 
|  | 107 | +                    depependency_node["requirements"] = ( | 
|  | 108 | +                        "[" | 
|  | 109 | +                        + depependency_node["requirements"] | 
|  | 110 | +                        .replace("=", "") | 
|  | 111 | +                        .replace(" ", "") | 
|  | 112 | +                        + "]" | 
|  | 113 | +                    ) | 
|  | 114 | +            if requirement_file_manager == "PIP": | 
|  | 115 | +                depependency_node["requirements"] = await parse_pip_constraints( | 
|  | 116 | +                    depependency_node["requirements"] | 
|  | 117 | +                ) | 
|  | 118 | +            all_packages[requirement_file_name]["dependencies"].update( | 
|  | 119 | +                {depependency_node["packageName"]: depependency_node["requirements"]} | 
|  | 120 | +            ) | 
|  | 121 | +    return (page_info, all_packages) | 
|  | 122 | + | 
|  | 123 | + | 
|  | 124 | +async def get_last_commit_date_github(owner: str, name: str) -> datetime: | 
|  | 125 | +    query = f""" | 
|  | 126 | +    {{ | 
|  | 127 | +        repository(owner: "{owner}", name: "{name}") {{ | 
|  | 128 | +            defaultBranchRef {{ | 
|  | 129 | +                target {{ | 
|  | 130 | +                    ... on Commit {{ | 
|  | 131 | +                        history(first: 1) {{ | 
|  | 132 | +                            edges {{ | 
|  | 133 | +                                node {{ | 
|  | 134 | +                                    author {{ | 
|  | 135 | +                                    date | 
|  | 136 | +                                    }} | 
|  | 137 | +                                }} | 
|  | 138 | +                            }} | 
|  | 139 | +                        }} | 
|  | 140 | +                    }} | 
|  | 141 | +                }} | 
|  | 142 | +            }} | 
|  | 143 | +        }} | 
|  | 144 | +    }} | 
|  | 145 | +    """ | 
|  | 146 | +    while True: | 
|  | 147 | +        try: | 
|  | 148 | +            response = post( | 
|  | 149 | +                "https://api.github.com/graphql", | 
|  | 150 | +                json={"query": query}, | 
|  | 151 | +                headers=headers_github, | 
|  | 152 | +            ).json() | 
|  | 153 | +            break | 
|  | 154 | +        except (ConnectTimeout, ConnectionError): | 
|  | 155 | +            sleep(5) | 
|  | 156 | +    return parse( | 
|  | 157 | +        response["data"]["repository"]["defaultBranchRef"]["target"]["history"][ | 
|  | 158 | +            "edges" | 
|  | 159 | +        ][0]["node"]["author"]["date"] | 
|  | 160 | +    ) | 
0 commit comments