1717import os
1818import requests
1919from typing import List , Optional
20+ from dataclasses import dataclass
2021
2122
2223class MissingGithubToken (ValueError ):
2324 """Raised when the GITHUB_TOKEN environment variable is not set"""
2425
2526 pass
2627
28+ RAW_CONTENT_BASE_URL = "https://raw.githubusercontent.com"
29+ MONO_REPO_PATH_FORMAT = "googleapis/google-cloud-python/main/packages/{repo_slug}"
30+ SPLIT_REPO_PATH_FORMAT = "{repo_slug}/main"
31+ REPO_METADATA_FILENAME = ".repo-metadata.json"
32+
33+
34+ # MONO_REPO defines the name of the mono repository for Python.
35+ MONO_REPO = "googleapis/google-cloud-python"
36+
37+ # REPO_EXCLUSION lists the repositories that need to be excluded.
38+ REPO_EXCLUSION = [
39+ # core libraries
40+ "googleapis/python-api-core" ,
41+ "googleapis/python-cloud-core" ,
42+ # proto only packages
43+ "googleapis/python-api-common-protos" ,
44+ # testing utilities
45+ "googleapis/python-test-utils" ,
46+ ]
47+
48+ # PACKAGE_RESPONSE_KEY defines the package name in the response.
49+ PACKAGE_RESPONSE_KEY = "name"
50+
51+ # REPO_RESPONSE_KEY defines the repository name in the response.
52+ REPO_RESPONSE_KEY = "full_name"
53+
54+ # ARCHIVED_RESPONSE_KEY defines the repository archived status in the response.
55+ ARCHIVED_RESPONSE_KEY = "archived"
56+
57+ # BASE_API defines the base API for Github.
58+ BASE_API = "https://api.github.com"
59+
60+
61+
62+
63+
2764class CloudClient :
2865 repo : str = None
2966 title : str = None
3067 release_level : str = None
3168 distribution_name : str = None
69+ issue_tracker : str = None
3270
3371 def __init__ (self , repo : dict ):
3472 self .repo = repo ["repo" ]
3573 # For now, strip out "Google Cloud" to standardize the titles
3674 self .title = repo ["name_pretty" ].replace ("Google " , "" ).replace ("Cloud " , "" )
3775 self .release_level = repo ["release_level" ]
3876 self .distribution_name = repo ["distribution_name" ]
77+ self .issue_tracker = repo .get ("issue_tracker" )
3978
4079 # For sorting, we want to sort by release level, then API pretty_name
4180 def __lt__ (self , other ):
@@ -48,6 +87,24 @@ def __repr__(self):
4887 return repr ((self .release_level , self .title ))
4988
5089
90+ @dataclass
91+ class Extractor :
92+ path_format : str
93+ response_key : str
94+
95+ def client_for_repo (self , repo_slug ) -> Optional [CloudClient ]:
96+ path = self .path_format .format (repo_slug = repo_slug )
97+ url = f"{ RAW_CONTENT_BASE_URL } /{ path } /{ REPO_METADATA_FILENAME } "
98+ response = requests .get (url )
99+ if response .status_code != requests .codes .ok :
100+ return
101+
102+ return CloudClient (response .json ())
103+
104+ def get_clients_from_batch_response (self , response_json ) -> List [CloudClient ]:
105+ return [self .client_for_repo (repo [self .response_key ]) for repo in response_json if allowed_repo (repo )]
106+
107+
51108def replace_content_in_readme (content_rows : List [str ]) -> None :
52109 START_MARKER = ".. API_TABLE_START"
53110 END_MARKER = ".. API_TABLE_END"
@@ -74,13 +131,20 @@ def replace_content_in_readme(content_rows: List[str]) -> None:
74131def client_row (client : CloudClient ) -> str :
75132 pypi_badge = f""".. |PyPI-{ client .distribution_name } | image:: https://img.shields.io/pypi/v/{ client .distribution_name } .svg
76133 :target: https://pypi.org/project/{ client .distribution_name } \n """
134+
135+ url = f"https://github.com/{ client .repo } "
136+ if client .repo == MONO_REPO :
137+ url += f"/tree/main/packages/{ client .distribution_name } "
77138
78139 content_row = [
79- f" * - `{ client .title } <https://github.com/ { client . repo } >`_\n " ,
80- f" - " + "|" + client .release_level + "| \n "
81- f" - |PyPI-{ client .distribution_name } |\n " ,
140+ f" * - `{ client .title } <{ url } >`_\n " ,
141+ f" - " + client .release_level + "\n " ,
142+ f" - |PyPI-{ client .distribution_name } |\n " ,
82143 ]
83144
145+ if client .issue_tracker :
146+ content_row .append (f" - `API Issues <{ client .issue_tracker } >`_\n " )
147+
84148 return (content_row , pypi_badge )
85149
86150
@@ -93,6 +157,7 @@ def generate_table_contents(clients: List[CloudClient]) -> List[str]:
93157 " * - Client\n " ,
94158 " - Release Level\n " ,
95159 " - Version\n " ,
160+ " - API Issue Tracker\n " ,
96161 ]
97162
98163 pypi_links = ["\n " ]
@@ -104,52 +169,30 @@ def generate_table_contents(clients: List[CloudClient]) -> List[str]:
104169 return content_rows + pypi_links
105170
106171
107- REPO_METADATA_URL_FORMAT = (
108- "https://raw.githubusercontent.com/{repo_slug}/main/.repo-metadata.json"
109- )
110-
111-
112- def client_for_repo (repo_slug ) -> Optional [CloudClient ]:
113- url = REPO_METADATA_URL_FORMAT .format (repo_slug = repo_slug )
114- response = requests .get (url )
115- if response .status_code != requests .codes .ok :
116- return
117-
118- return CloudClient (response .json ())
119-
120- REPO_EXCLUSION = [
121- # core libraries
122- "googleapis/python-api-core" ,
123- "googleapis/python-cloud-core" ,
124- # proto only packages
125- "googleapis/python-org-policy" ,
126- "googleapis/python-os-config" ,
127- "googleapis/python-access-context-manager" ,
128- "googleapis/python-api-common-protos" ,
129- # testing utilities
130- "googleapis/python-test-utils" ,
131- ]
132-
133-
134172def allowed_repo (repo ) -> bool :
135- return (
136- repo ["full_name" ].startswith ("googleapis/python-" )
137- and repo ["full_name" ] not in REPO_EXCLUSION
138- and not repo ["archived" ]
173+ return REPO_RESPONSE_KEY not in repo or (
174+ repo [REPO_RESPONSE_KEY ].startswith ("googleapis/python-" )
175+ and repo [REPO_RESPONSE_KEY ] not in REPO_EXCLUSION
176+ and not repo [ARCHIVED_RESPONSE_KEY ]
139177 )
140178
141179
142- def get_clients_batch_from_response_json (response_json ) -> List [CloudClient ]:
143- return [client_for_repo (repo ["full_name" ]) for repo in response_json if allowed_repo (repo )]
180+ def mono_repo_clients (token : str ) -> List [CloudClient ]:
181+ # all mono repo clients
182+ url = f"{ BASE_API } /repos/{ MONO_REPO } /contents/packages"
183+ headers = {'Authorization' : f'token { token } ' }
184+ response = requests .get (url = url , headers = headers )
185+ mono_repo_extractor = Extractor (path_format = MONO_REPO_PATH_FORMAT , response_key = PACKAGE_RESPONSE_KEY )
186+
187+ return mono_repo_extractor .get_clients_from_batch_response (response .json ())
144188
145- def all_clients () -> List [CloudClient ]:
146- clients = []
147- first_request = True
148- token = os .environ ['GITHUB_TOKEN' ]
149189
190+ def split_repo_clients (token : str ) -> List [CloudClient ]:
191+
192+ first_request = True
150193 while first_request or 'next' in response .links :
151194 if first_request :
152- url = "https://api.github.com /search/repositories?page=1"
195+ url = f" { BASE_API } /search/repositories?page=1"
153196 first_request = False
154197 else :
155198 url = response .links ['next' ]['url' ]
@@ -159,13 +202,29 @@ def all_clients() -> List[CloudClient]:
159202 repositories = response .json ().get ("items" , [])
160203 if len (repositories ) == 0 :
161204 break
162- clients .extend (get_clients_batch_from_response_json (repositories ))
205+
206+ split_repo_extractor = Extractor (path_format = SPLIT_REPO_PATH_FORMAT , response_key = REPO_RESPONSE_KEY )
207+ return split_repo_extractor .get_clients_from_batch_response (repositories )
208+
209+
210+ def get_token ():
211+ if 'GITHUB_TOKEN' not in os .environ :
212+ raise MissingGithubToken ("Please include a GITHUB_TOKEN env var." )
213+
214+ token = os .environ ['GITHUB_TOKEN' ]
215+ return token
216+
217+
218+ def all_clients () -> List [CloudClient ]:
219+ clients = []
220+ token = get_token ()
221+
222+ clients .extend (split_repo_clients (token ))
223+ clients .extend (mono_repo_clients (token ))
163224
164225 # remove empty clients
165226 return [client for client in clients if client ]
166227
167- if 'GITHUB_TOKEN' not in os .environ :
168- raise MissingGithubToken ("Please include a GITHUB_TOKEN env var." )
169228
170229clients = sorted (all_clients ())
171230table_contents = generate_table_contents (clients )
0 commit comments