commons-metrics 0.0.24__tar.gz → 0.0.25__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {commons_metrics-0.0.24 → commons_metrics-0.0.25}/PKG-INFO +1 -1
- {commons_metrics-0.0.24 → commons_metrics-0.0.25}/commons_metrics/__init__.py +1 -1
- {commons_metrics-0.0.24 → commons_metrics-0.0.25}/commons_metrics/github_api_client.py +257 -122
- {commons_metrics-0.0.24 → commons_metrics-0.0.25}/commons_metrics.egg-info/PKG-INFO +1 -1
- {commons_metrics-0.0.24 → commons_metrics-0.0.25}/setup.py +1 -1
- {commons_metrics-0.0.24 → commons_metrics-0.0.25}/LICENSE +0 -0
- {commons_metrics-0.0.24 → commons_metrics-0.0.25}/README.md +0 -0
- {commons_metrics-0.0.24 → commons_metrics-0.0.25}/commons_metrics/azure_devops_client.py +0 -0
- {commons_metrics-0.0.24 → commons_metrics-0.0.25}/commons_metrics/cache_manager.py +0 -0
- {commons_metrics-0.0.24 → commons_metrics-0.0.25}/commons_metrics/commons_repos_client.py +0 -0
- {commons_metrics-0.0.24 → commons_metrics-0.0.25}/commons_metrics/database.py +0 -0
- {commons_metrics-0.0.24 → commons_metrics-0.0.25}/commons_metrics/date_utils.py +0 -0
- {commons_metrics-0.0.24 → commons_metrics-0.0.25}/commons_metrics/repositories.py +0 -0
- {commons_metrics-0.0.24 → commons_metrics-0.0.25}/commons_metrics/s3_file_manager.py +0 -0
- {commons_metrics-0.0.24 → commons_metrics-0.0.25}/commons_metrics/text_simplifier.py +0 -0
- {commons_metrics-0.0.24 → commons_metrics-0.0.25}/commons_metrics/update_design_components.py +0 -0
- {commons_metrics-0.0.24 → commons_metrics-0.0.25}/commons_metrics/util.py +0 -0
- {commons_metrics-0.0.24 → commons_metrics-0.0.25}/commons_metrics/variable_finder.py +0 -0
- {commons_metrics-0.0.24 → commons_metrics-0.0.25}/commons_metrics.egg-info/SOURCES.txt +0 -0
- {commons_metrics-0.0.24 → commons_metrics-0.0.25}/commons_metrics.egg-info/dependency_links.txt +0 -0
- {commons_metrics-0.0.24 → commons_metrics-0.0.25}/commons_metrics.egg-info/requires.txt +0 -0
- {commons_metrics-0.0.24 → commons_metrics-0.0.25}/commons_metrics.egg-info/top_level.txt +0 -0
- {commons_metrics-0.0.24 → commons_metrics-0.0.25}/setup.cfg +0 -0
|
@@ -12,4 +12,4 @@ from .text_simplifier import TextSimplifier
|
|
|
12
12
|
from .variable_finder import VariableFinder
|
|
13
13
|
|
|
14
14
|
__all__ = ['Util', 'DatabaseConnection', 'ComponentRepository', 'UpdateDesignSystemComponents', 'GitHubAPIClient', 'AzureDevOpsClient', 'S3FileManager', 'CacheManager', 'CommonsReposClient', 'DateUtils', 'TextSimplifier', 'VariableFinder']
|
|
15
|
-
__version__ = '0.0.
|
|
15
|
+
__version__ = '0.0.25'
|
|
@@ -11,23 +11,93 @@ class RateLimiter:
|
|
|
11
11
|
"""
|
|
12
12
|
Controla el rate limiting para respetar los límites de API de GitHub.
|
|
13
13
|
GitHub permite 5000 requests/hora autenticados = ~1.4 TPS.
|
|
14
|
-
|
|
14
|
+
|
|
15
|
+
Estrategia adaptativa:
|
|
16
|
+
1. Cuando está cerca del límite (>90%), aumenta delays si falta >10min para reset
|
|
17
|
+
2. Si faltan <10min para reset, reduce delay a 10ms para usar toda la cuota
|
|
18
|
+
3. Exponential backoff solo para errores 403/429 de rate limit
|
|
15
19
|
"""
|
|
16
|
-
def __init__(self, delay: float = 0.72):
|
|
17
|
-
self.
|
|
20
|
+
def __init__(self, delay: float = 0.72, token: str = None):
|
|
21
|
+
self.base_delay = delay
|
|
22
|
+
self.current_delay = delay
|
|
18
23
|
self.last_request_time = 0
|
|
19
24
|
self.lock = Lock()
|
|
20
|
-
|
|
25
|
+
self.token = token
|
|
26
|
+
self.last_rate_check = 0
|
|
27
|
+
self.rate_check_interval = 10 # Verificar rate limit cada 10 requests
|
|
28
|
+
self.request_count = 0
|
|
29
|
+
# Variables globales para rate limit
|
|
30
|
+
self.remaining_requests = None
|
|
31
|
+
self.limit = None
|
|
32
|
+
self.reset_time = None
|
|
33
|
+
|
|
34
|
+
def check_rate_limit(self):
|
|
35
|
+
"""Obtiene el estado actual del rate limit desde GitHub y actualiza variables globales"""
|
|
36
|
+
if not self.token:
|
|
37
|
+
return None, None, None
|
|
38
|
+
|
|
39
|
+
try:
|
|
40
|
+
response = requests.get(
|
|
41
|
+
"https://api.github.com/rate_limit",
|
|
42
|
+
headers={"Authorization": f"token {self.token}"}
|
|
43
|
+
)
|
|
44
|
+
if response.status_code == 200:
|
|
45
|
+
data = response.json()
|
|
46
|
+
self.remaining_requests = data['rate']['remaining']
|
|
47
|
+
self.limit = data['rate']['limit']
|
|
48
|
+
self.reset_time = data['rate']['reset']
|
|
49
|
+
return self.remaining_requests, self.limit, self.reset_time
|
|
50
|
+
except Exception as e:
|
|
51
|
+
print(f"Warning: No se pudo verificar rate limit: {e}")
|
|
52
|
+
|
|
53
|
+
return None, None, None
|
|
54
|
+
|
|
55
|
+
def adjust_delay(self):
|
|
56
|
+
"""
|
|
57
|
+
Ajusta el delay dinámicamente basado en el rate limit actual:
|
|
58
|
+
- >90% usado y falta >10min: aumentar delay
|
|
59
|
+
- <10min para reset: reducir delay a 10ms
|
|
60
|
+
"""
|
|
61
|
+
remaining, limit, reset_time = self.check_rate_limit()
|
|
62
|
+
|
|
63
|
+
if remaining is None or limit is None or reset_time is None:
|
|
64
|
+
return
|
|
65
|
+
|
|
66
|
+
usage_percent = ((limit - remaining) / limit) * 100
|
|
67
|
+
time_to_reset = reset_time - time.time()
|
|
68
|
+
minutes_to_reset = time_to_reset / 60
|
|
69
|
+
|
|
70
|
+
# Estrategia 1: Cerca del límite (>90%) y falta >10min
|
|
71
|
+
if usage_percent > 90 and minutes_to_reset > 10:
|
|
72
|
+
# Aumentar delay progresivamente
|
|
73
|
+
self.current_delay = self.base_delay * 2
|
|
74
|
+
print(f"⚠️ Rate limit alto ({usage_percent:.1f}%). Aumentando delay a {self.current_delay:.2f}s")
|
|
75
|
+
|
|
76
|
+
# Estrategia 2: Faltan <10min para reset
|
|
77
|
+
elif minutes_to_reset < 10:
|
|
78
|
+
# Reducir delay para aprovechar requests restantes
|
|
79
|
+
self.current_delay = 0.01 # 10ms
|
|
80
|
+
print(f"🚀 Quedan {minutes_to_reset:.1f}min para reset. Acelerando a {self.current_delay*1000:.0f}ms")
|
|
81
|
+
|
|
82
|
+
# Normal: restaurar delay base
|
|
83
|
+
else:
|
|
84
|
+
self.current_delay = self.base_delay
|
|
85
|
+
|
|
21
86
|
def wait(self):
|
|
22
87
|
"""Espera el tiempo necesario antes de hacer el siguiente request"""
|
|
23
88
|
with self.lock:
|
|
89
|
+
# Verificar y ajustar delay cada N requests
|
|
90
|
+
self.request_count += 1
|
|
91
|
+
if self.request_count % self.rate_check_interval == 0:
|
|
92
|
+
self.adjust_delay()
|
|
93
|
+
|
|
24
94
|
current_time = time.time()
|
|
25
95
|
time_since_last_request = current_time - self.last_request_time
|
|
26
|
-
|
|
27
|
-
if time_since_last_request < self.
|
|
28
|
-
sleep_time = self.
|
|
96
|
+
|
|
97
|
+
if time_since_last_request < self.current_delay:
|
|
98
|
+
sleep_time = self.current_delay - time_since_last_request
|
|
29
99
|
time.sleep(sleep_time)
|
|
30
|
-
|
|
100
|
+
|
|
31
101
|
self.last_request_time = time.time()
|
|
32
102
|
|
|
33
103
|
|
|
@@ -35,15 +105,20 @@ class GitHubAPIClient:
|
|
|
35
105
|
"""
|
|
36
106
|
Cliente para interactuar con la API de GitHub con rate limiting integrado.
|
|
37
107
|
Controla automáticamente el TPS para no exceder los límites de GitHub (5000 req/hora).
|
|
108
|
+
|
|
109
|
+
Incluye:
|
|
110
|
+
- Rate limiting adaptativo (ajusta delays según uso)
|
|
111
|
+
- Exponential backoff para errores 403/429
|
|
112
|
+
- Optimización de requests en últimos 10 minutos antes de reset
|
|
38
113
|
"""
|
|
39
|
-
|
|
114
|
+
|
|
40
115
|
# Rate limiter compartido entre todas las instancias
|
|
41
|
-
_rate_limiter =
|
|
42
|
-
|
|
116
|
+
_rate_limiter = None
|
|
117
|
+
|
|
43
118
|
def __init__(self, token: str, owner: str = None, repo: str = None, enable_rate_limit: bool = True):
|
|
44
119
|
"""
|
|
45
120
|
Inicializa el cliente de GitHub API
|
|
46
|
-
|
|
121
|
+
|
|
47
122
|
Args:
|
|
48
123
|
token: Personal Access Token de GitHub
|
|
49
124
|
owner: Dueño del repositorio (ej: 'grupobancolombia-innersource') - Opcional
|
|
@@ -59,46 +134,109 @@ class GitHubAPIClient:
|
|
|
59
134
|
"Authorization": f"token {token}",
|
|
60
135
|
"Accept": "application/vnd.github.v3+json"
|
|
61
136
|
}
|
|
62
|
-
|
|
137
|
+
|
|
138
|
+
# Inicializar rate limiter compartido con token
|
|
139
|
+
if GitHubAPIClient._rate_limiter is None:
|
|
140
|
+
GitHubAPIClient._rate_limiter = RateLimiter(delay=0.72, token=token)
|
|
141
|
+
|
|
142
|
+
def _request_with_backoff(self, method: str, url: str, max_retries: int = 5, **kwargs):
|
|
143
|
+
"""
|
|
144
|
+
Hace un request con exponential backoff SOLO para errores de rate limit (403/429)
|
|
145
|
+
|
|
146
|
+
Estrategia:
|
|
147
|
+
- Si hay X-RateLimit-Reset header, espera hasta ese momento
|
|
148
|
+
- Si no, usa exponential backoff: 2^attempt segundos (1s, 2s, 4s, 8s, 16s)
|
|
149
|
+
- Máximo 5 reintentos
|
|
150
|
+
|
|
151
|
+
Args:
|
|
152
|
+
method: Método HTTP ('get', 'post', etc)
|
|
153
|
+
url: URL del endpoint
|
|
154
|
+
max_retries: Número máximo de reintentos (default: 5)
|
|
155
|
+
**kwargs: Argumentos adicionales para requests
|
|
156
|
+
|
|
157
|
+
Returns:
|
|
158
|
+
Response object
|
|
159
|
+
|
|
160
|
+
Raises:
|
|
161
|
+
Exception: Si no es error de rate limit o se agotan los reintentos
|
|
162
|
+
"""
|
|
163
|
+
request_func = getattr(requests, method.lower())
|
|
164
|
+
|
|
165
|
+
for attempt in range(max_retries):
|
|
166
|
+
if self.enable_rate_limit:
|
|
167
|
+
self._rate_limiter.wait()
|
|
168
|
+
|
|
169
|
+
response = request_func(url, **kwargs)
|
|
170
|
+
|
|
171
|
+
# Verificar si es específicamente un error de rate limit
|
|
172
|
+
if response.status_code in [403, 429]:
|
|
173
|
+
# Verificar que sea rate limit y no otro error 403
|
|
174
|
+
if 'rate limit' in response.text.lower() or response.status_code == 429:
|
|
175
|
+
if attempt < max_retries - 1:
|
|
176
|
+
# Usar el reset_time global del RateLimiter
|
|
177
|
+
reset_time = self._rate_limiter.reset_time
|
|
178
|
+
|
|
179
|
+
if reset_time:
|
|
180
|
+
# Esperar hasta el reset (con 5 segundos extra de margen)
|
|
181
|
+
wait_time = reset_time - time.time() + 5
|
|
182
|
+
if wait_time > 0:
|
|
183
|
+
wait_minutes = wait_time / 60
|
|
184
|
+
print(f"⚠️ Rate limit alcanzado. Esperando {wait_minutes:.1f} minutos hasta reset...")
|
|
185
|
+
time.sleep(wait_time)
|
|
186
|
+
# Verificar rate limit actualizado después de esperar
|
|
187
|
+
self._rate_limiter.check_rate_limit()
|
|
188
|
+
continue
|
|
189
|
+
else:
|
|
190
|
+
# Si no hay reset_time global, usar exponential backoff
|
|
191
|
+
# 1s, 2s, 4s, 8s, 16s (máx 60s)
|
|
192
|
+
wait_time = min(2 ** attempt, 60)
|
|
193
|
+
print(f"⚠️ Rate limit alcanzado. Esperando {wait_time}s (intento {attempt + 1}/{max_retries})...")
|
|
194
|
+
time.sleep(wait_time)
|
|
195
|
+
continue
|
|
196
|
+
else:
|
|
197
|
+
raise Exception(f"Rate limit excedido después de {max_retries} intentos")
|
|
198
|
+
else:
|
|
199
|
+
# Es un 403 pero NO de rate limit - no reintentar
|
|
200
|
+
raise Exception(f"Error 403 (no rate limit): {response.text}")
|
|
201
|
+
|
|
202
|
+
# Si es exitoso o cualquier otro error, retornar
|
|
203
|
+
return response
|
|
204
|
+
|
|
205
|
+
return response
|
|
206
|
+
|
|
63
207
|
def get_directory_contents(self, path: str = "") -> List[Dict]:
|
|
64
208
|
"""
|
|
65
209
|
Obtiene el contenido de un directorio en el repositorio
|
|
66
|
-
|
|
210
|
+
|
|
67
211
|
Args:
|
|
68
212
|
path: Ruta del directorio (ej: 'lib/atoms')
|
|
69
|
-
|
|
213
|
+
|
|
70
214
|
Returns:
|
|
71
215
|
Lista de diccionarios con información de archivos/carpetas
|
|
72
216
|
"""
|
|
73
|
-
if self.enable_rate_limit:
|
|
74
|
-
self._rate_limiter.wait()
|
|
75
|
-
|
|
76
217
|
url = f"{self.base_url}/contents/{path}"
|
|
77
|
-
response =
|
|
78
|
-
|
|
218
|
+
response = self._request_with_backoff('get', url, headers=self.headers)
|
|
219
|
+
|
|
79
220
|
if response.status_code == 200:
|
|
80
221
|
return response.json()
|
|
81
222
|
elif response.status_code == 404:
|
|
82
223
|
return []
|
|
83
224
|
else:
|
|
84
225
|
raise Exception(f"Error getting directory contents: {response.status_code} - {response.text}")
|
|
85
|
-
|
|
226
|
+
|
|
86
227
|
def get_file_content(self, path: str) -> Optional[str]:
|
|
87
228
|
"""
|
|
88
229
|
Obtiene el contenido de un archivo
|
|
89
|
-
|
|
230
|
+
|
|
90
231
|
Args:
|
|
91
232
|
path: Ruta del archivo en el repositorio
|
|
92
|
-
|
|
233
|
+
|
|
93
234
|
Returns:
|
|
94
235
|
Contenido del archivo como string, o None si no existe
|
|
95
236
|
"""
|
|
96
|
-
if self.enable_rate_limit:
|
|
97
|
-
self._rate_limiter.wait()
|
|
98
|
-
|
|
99
237
|
url = f"{self.base_url}/contents/{path}"
|
|
100
|
-
response =
|
|
101
|
-
|
|
238
|
+
response = self._request_with_backoff('get', url, headers=self.headers)
|
|
239
|
+
|
|
102
240
|
if response.status_code == 200:
|
|
103
241
|
content = response.json()
|
|
104
242
|
if content.get('encoding') == 'base64':
|
|
@@ -109,142 +247,140 @@ class GitHubAPIClient:
|
|
|
109
247
|
return None
|
|
110
248
|
else:
|
|
111
249
|
raise Exception(f"Error getting file content: {response.status_code} - {response.text}")
|
|
112
|
-
|
|
250
|
+
|
|
113
251
|
def list_folders_in_directory(self, path: str) -> List[str]:
|
|
114
252
|
"""
|
|
115
253
|
Lista solo las carpetas dentro de un directorio
|
|
116
|
-
|
|
254
|
+
|
|
117
255
|
Args:
|
|
118
256
|
path: Ruta del directorio
|
|
119
|
-
|
|
257
|
+
|
|
120
258
|
Returns:
|
|
121
259
|
Lista de nombres de carpetas
|
|
122
260
|
"""
|
|
123
261
|
contents = self.get_directory_contents(path)
|
|
124
262
|
folders = [
|
|
125
|
-
item['name']
|
|
126
|
-
for item in contents
|
|
263
|
+
item['name']
|
|
264
|
+
for item in contents
|
|
127
265
|
if item['type'] == 'dir'
|
|
128
266
|
]
|
|
129
267
|
return folders
|
|
130
|
-
|
|
268
|
+
|
|
131
269
|
def walk_directory(self, path: str = "", extension: str = None, exclude_patterns: List[str] = None) -> List[Dict]:
|
|
132
270
|
"""
|
|
133
271
|
Recorre recursivamente un directorio y retorna todos los archivos
|
|
134
|
-
|
|
272
|
+
|
|
135
273
|
Args:
|
|
136
274
|
path: Ruta del directorio inicial
|
|
137
275
|
extension: Extensión a filtrar (ej: '.ts', '.dart')
|
|
138
276
|
exclude_patterns: Lista de patrones a excluir (ej: ['.spec.', '.test.', '.d.ts'])
|
|
139
|
-
|
|
277
|
+
|
|
140
278
|
Returns:
|
|
141
279
|
Lista de diccionarios con información de archivos encontrados
|
|
142
280
|
"""
|
|
143
281
|
all_files = []
|
|
144
282
|
exclude_patterns = exclude_patterns or []
|
|
145
|
-
|
|
283
|
+
|
|
146
284
|
def should_exclude(filename: str) -> bool:
|
|
147
285
|
return any(pattern in filename for pattern in exclude_patterns)
|
|
148
|
-
|
|
286
|
+
|
|
149
287
|
def recurse_directory(current_path: str):
|
|
150
288
|
contents = self.get_directory_contents(current_path)
|
|
151
|
-
|
|
289
|
+
|
|
152
290
|
for item in contents:
|
|
153
291
|
item_path = f"{current_path}/{item['name']}" if current_path else item['name']
|
|
154
|
-
|
|
292
|
+
|
|
155
293
|
if item['type'] == 'file':
|
|
156
294
|
# Aplicar filtros de extensión y exclusión
|
|
157
295
|
if extension and not item['name'].endswith(extension):
|
|
158
296
|
continue
|
|
159
297
|
if should_exclude(item['name']):
|
|
160
298
|
continue
|
|
161
|
-
|
|
299
|
+
|
|
162
300
|
all_files.append({
|
|
163
301
|
'name': item['name'],
|
|
164
302
|
'path': item_path,
|
|
165
303
|
'url': item.get('url', ''),
|
|
166
304
|
'download_url': item.get('download_url', '')
|
|
167
305
|
})
|
|
168
|
-
|
|
306
|
+
|
|
169
307
|
elif item['type'] == 'dir':
|
|
170
308
|
# Excluir directorios comunes que no contienen componentes
|
|
171
309
|
if item['name'] not in ['node_modules', 'dist', 'build', '.git', 'test', 'tests', '__pycache__']:
|
|
172
310
|
recurse_directory(item_path)
|
|
173
|
-
|
|
311
|
+
|
|
174
312
|
recurse_directory(path)
|
|
175
313
|
return all_files
|
|
176
|
-
|
|
314
|
+
|
|
177
315
|
def search_code(self, query: str, per_page: int = 100) -> List[Dict]:
|
|
178
316
|
"""
|
|
179
317
|
Busca código en GitHub usando la API de búsqueda
|
|
180
|
-
|
|
318
|
+
|
|
181
319
|
Args:
|
|
182
320
|
query: Query de búsqueda (ej: '"bds_mobile" in:file filename:pubspec.yaml')
|
|
183
321
|
per_page: Número de resultados por página (máximo 100)
|
|
184
|
-
|
|
322
|
+
|
|
185
323
|
Returns:
|
|
186
324
|
Lista de diccionarios con información de archivos encontrados
|
|
187
325
|
"""
|
|
188
326
|
all_results = []
|
|
189
327
|
page = 1
|
|
190
|
-
|
|
328
|
+
|
|
191
329
|
while True:
|
|
192
|
-
if self.enable_rate_limit:
|
|
193
|
-
self._rate_limiter.wait()
|
|
194
|
-
|
|
195
330
|
url = "https://api.github.com/search/code"
|
|
196
331
|
params = {
|
|
197
332
|
'q': query,
|
|
198
333
|
'per_page': per_page,
|
|
199
334
|
'page': page
|
|
200
335
|
}
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
336
|
+
|
|
337
|
+
try:
|
|
338
|
+
response = self._request_with_backoff('get', url, headers=self.headers, params=params)
|
|
339
|
+
|
|
340
|
+
if response.status_code == 200:
|
|
341
|
+
data = response.json()
|
|
342
|
+
items = data.get('items', [])
|
|
343
|
+
|
|
344
|
+
if not items:
|
|
345
|
+
break
|
|
346
|
+
|
|
347
|
+
all_results.extend(items)
|
|
348
|
+
|
|
349
|
+
# Si hay menos items que per_page, es la última página
|
|
350
|
+
if len(items) < per_page:
|
|
351
|
+
break
|
|
352
|
+
|
|
353
|
+
page += 1
|
|
354
|
+
else:
|
|
355
|
+
raise Exception(f"Error searching code: {response.status_code} - {response.text}")
|
|
356
|
+
|
|
357
|
+
except Exception as e:
|
|
358
|
+
print(f"⚠️ Error en búsqueda: {e}. Resultados obtenidos: {len(all_results)}")
|
|
221
359
|
break
|
|
222
|
-
|
|
223
|
-
raise Exception(f"Error searching code: {response.status_code} - {response.text}")
|
|
224
|
-
|
|
360
|
+
|
|
225
361
|
return all_results
|
|
226
|
-
|
|
362
|
+
|
|
227
363
|
def search_projects_with_bds(self, platform: str, design_system_name: str = None) -> List[Dict]:
|
|
228
364
|
"""
|
|
229
365
|
Busca proyectos que usan el sistema de diseño BDS (mobile o web)
|
|
230
|
-
|
|
366
|
+
|
|
231
367
|
Args:
|
|
232
368
|
platform: 'mobile' o 'web'
|
|
233
369
|
design_system_name: Nombre del paquete del sistema de diseño
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
370
|
+
Si no se proporciona, usa valores por defecto:
|
|
371
|
+
- mobile: "bds_mobile"
|
|
372
|
+
- web: "@bancolombia/design-system-web"
|
|
373
|
+
|
|
238
374
|
Returns:
|
|
239
375
|
Lista de proyectos con información del repositorio incluyendo la versión
|
|
240
|
-
|
|
376
|
+
|
|
241
377
|
Raises:
|
|
242
378
|
ValueError: Si platform no es 'mobile' o 'web'
|
|
243
379
|
"""
|
|
244
380
|
# Validar plataforma
|
|
245
381
|
if platform not in ['mobile', 'web']:
|
|
246
382
|
raise ValueError(f"Platform debe ser 'mobile' o 'web', se recibió: {platform}")
|
|
247
|
-
|
|
383
|
+
|
|
248
384
|
# Configurar valores según la plataforma
|
|
249
385
|
if platform == 'mobile':
|
|
250
386
|
config_file = 'pubspec.yaml'
|
|
@@ -254,21 +390,21 @@ class GitHubAPIClient:
|
|
|
254
390
|
config_file = 'package.json'
|
|
255
391
|
default_package_name = '@bancolombia/design-system-web'
|
|
256
392
|
extract_version_method = CommonsReposClient.extract_package_version_from_package_json
|
|
257
|
-
|
|
393
|
+
|
|
258
394
|
# Usar nombre de paquete por defecto si no se proporciona
|
|
259
395
|
package_name = design_system_name or default_package_name
|
|
260
|
-
|
|
396
|
+
|
|
261
397
|
# Agregar filtro de organización si self.owner está definido
|
|
262
398
|
org_filter = f" org:{self.owner}" if self.owner else ""
|
|
263
399
|
query = f'"{package_name}" in:file filename:{config_file}{org_filter}'
|
|
264
400
|
results = self.search_code(query)
|
|
265
|
-
|
|
401
|
+
|
|
266
402
|
projects = []
|
|
267
403
|
for item in results:
|
|
268
404
|
# Obtener contenido del archivo de configuración para extraer la versión
|
|
269
405
|
file_content = self._get_file_content_from_url(item['url'])
|
|
270
406
|
version = extract_version_method(file_content, package_name)
|
|
271
|
-
|
|
407
|
+
|
|
272
408
|
project_info = {
|
|
273
409
|
'name': item['repository']['name'],
|
|
274
410
|
'full_name': item['repository']['full_name'],
|
|
@@ -278,72 +414,71 @@ class GitHubAPIClient:
|
|
|
278
414
|
'bds_version': version
|
|
279
415
|
}
|
|
280
416
|
projects.append(project_info)
|
|
281
|
-
|
|
417
|
+
|
|
282
418
|
return projects
|
|
283
|
-
|
|
419
|
+
|
|
284
420
|
def search_repositories(self, query: str, per_page: int = 100) -> List[Dict]:
|
|
285
421
|
"""
|
|
286
422
|
Busca repositorios en GitHub usando la API de búsqueda
|
|
287
|
-
|
|
423
|
+
|
|
288
424
|
Args:
|
|
289
425
|
query: Query de búsqueda (ej: 'NU0296001 mobile in:name org:grupobancolombia-innersource')
|
|
290
426
|
per_page: Número de resultados por página (máximo 100)
|
|
291
|
-
|
|
427
|
+
|
|
292
428
|
Returns:
|
|
293
429
|
Lista de diccionarios con información de repositorios encontrados
|
|
294
430
|
"""
|
|
295
431
|
all_results = []
|
|
296
432
|
page = 1
|
|
297
433
|
search_url = "https://api.github.com/search/repositories"
|
|
298
|
-
|
|
434
|
+
|
|
299
435
|
while True:
|
|
300
|
-
if self.enable_rate_limit:
|
|
301
|
-
self._rate_limiter.wait()
|
|
302
|
-
|
|
303
436
|
params = {
|
|
304
437
|
'q': query,
|
|
305
438
|
'per_page': per_page,
|
|
306
439
|
'page': page
|
|
307
440
|
}
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
441
|
+
|
|
442
|
+
try:
|
|
443
|
+
response = self._request_with_backoff('get', search_url, headers=self.headers, params=params)
|
|
444
|
+
|
|
445
|
+
if response.status_code != 200:
|
|
446
|
+
print(f"Error searching repositories: {response.status_code}")
|
|
447
|
+
print(f"Response: {response.text}")
|
|
448
|
+
break
|
|
449
|
+
|
|
450
|
+
data = response.json()
|
|
451
|
+
items = data.get('items', [])
|
|
452
|
+
|
|
453
|
+
if not items:
|
|
454
|
+
break
|
|
455
|
+
|
|
456
|
+
all_results.extend(items)
|
|
457
|
+
|
|
458
|
+
# Verificar si hay más páginas
|
|
459
|
+
if len(items) < per_page:
|
|
460
|
+
break
|
|
461
|
+
|
|
462
|
+
page += 1
|
|
463
|
+
|
|
464
|
+
except Exception as e:
|
|
465
|
+
print(f"⚠️ Error buscando repositorios: {e}")
|
|
326
466
|
break
|
|
327
|
-
|
|
328
|
-
page += 1
|
|
329
|
-
|
|
467
|
+
|
|
330
468
|
return all_results
|
|
331
|
-
|
|
469
|
+
|
|
332
470
|
def _get_file_content_from_url(self, api_url: str) -> Optional[str]:
|
|
333
471
|
"""
|
|
334
472
|
Obtiene el contenido de un archivo desde una URL de la API de GitHub
|
|
335
|
-
|
|
473
|
+
|
|
336
474
|
Args:
|
|
337
475
|
api_url: URL de la API de GitHub para el archivo
|
|
338
|
-
|
|
476
|
+
|
|
339
477
|
Returns:
|
|
340
478
|
Contenido del archivo como string, o None si no existe
|
|
341
479
|
"""
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
response = requests.get(api_url, headers=self.headers)
|
|
346
|
-
|
|
480
|
+
response = self._request_with_backoff('get', api_url, headers=self.headers)
|
|
481
|
+
|
|
347
482
|
if response.status_code == 200:
|
|
348
483
|
content = response.json()
|
|
349
484
|
if content.get('encoding') == 'base64':
|
|
@@ -2,7 +2,7 @@ from setuptools import setup, find_packages
|
|
|
2
2
|
|
|
3
3
|
setup(
|
|
4
4
|
name='commons_metrics',
|
|
5
|
-
version='0.0.
|
|
5
|
+
version='0.0.25',
|
|
6
6
|
description='A simple library for basic statistical calculations',
|
|
7
7
|
#long_description=open('USAGE.md').read(),
|
|
8
8
|
#long_description_content_type='text/markdown',
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{commons_metrics-0.0.24 → commons_metrics-0.0.25}/commons_metrics/update_design_components.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{commons_metrics-0.0.24 → commons_metrics-0.0.25}/commons_metrics.egg-info/dependency_links.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|