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.
Files changed (23) hide show
  1. {commons_metrics-0.0.24 → commons_metrics-0.0.25}/PKG-INFO +1 -1
  2. {commons_metrics-0.0.24 → commons_metrics-0.0.25}/commons_metrics/__init__.py +1 -1
  3. {commons_metrics-0.0.24 → commons_metrics-0.0.25}/commons_metrics/github_api_client.py +257 -122
  4. {commons_metrics-0.0.24 → commons_metrics-0.0.25}/commons_metrics.egg-info/PKG-INFO +1 -1
  5. {commons_metrics-0.0.24 → commons_metrics-0.0.25}/setup.py +1 -1
  6. {commons_metrics-0.0.24 → commons_metrics-0.0.25}/LICENSE +0 -0
  7. {commons_metrics-0.0.24 → commons_metrics-0.0.25}/README.md +0 -0
  8. {commons_metrics-0.0.24 → commons_metrics-0.0.25}/commons_metrics/azure_devops_client.py +0 -0
  9. {commons_metrics-0.0.24 → commons_metrics-0.0.25}/commons_metrics/cache_manager.py +0 -0
  10. {commons_metrics-0.0.24 → commons_metrics-0.0.25}/commons_metrics/commons_repos_client.py +0 -0
  11. {commons_metrics-0.0.24 → commons_metrics-0.0.25}/commons_metrics/database.py +0 -0
  12. {commons_metrics-0.0.24 → commons_metrics-0.0.25}/commons_metrics/date_utils.py +0 -0
  13. {commons_metrics-0.0.24 → commons_metrics-0.0.25}/commons_metrics/repositories.py +0 -0
  14. {commons_metrics-0.0.24 → commons_metrics-0.0.25}/commons_metrics/s3_file_manager.py +0 -0
  15. {commons_metrics-0.0.24 → commons_metrics-0.0.25}/commons_metrics/text_simplifier.py +0 -0
  16. {commons_metrics-0.0.24 → commons_metrics-0.0.25}/commons_metrics/update_design_components.py +0 -0
  17. {commons_metrics-0.0.24 → commons_metrics-0.0.25}/commons_metrics/util.py +0 -0
  18. {commons_metrics-0.0.24 → commons_metrics-0.0.25}/commons_metrics/variable_finder.py +0 -0
  19. {commons_metrics-0.0.24 → commons_metrics-0.0.25}/commons_metrics.egg-info/SOURCES.txt +0 -0
  20. {commons_metrics-0.0.24 → commons_metrics-0.0.25}/commons_metrics.egg-info/dependency_links.txt +0 -0
  21. {commons_metrics-0.0.24 → commons_metrics-0.0.25}/commons_metrics.egg-info/requires.txt +0 -0
  22. {commons_metrics-0.0.24 → commons_metrics-0.0.25}/commons_metrics.egg-info/top_level.txt +0 -0
  23. {commons_metrics-0.0.24 → commons_metrics-0.0.25}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: commons_metrics
3
- Version: 0.0.24
3
+ Version: 0.0.25
4
4
  Summary: A simple library for basic statistical calculations
5
5
  Author: Bancolombia
6
6
  Author-email: omar.david.pino@email.com
@@ -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.24'
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
- Usamos 720ms entre requests (1.39 TPS) para estar seguros.
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.delay = delay
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.delay:
28
- sleep_time = self.delay - time_since_last_request
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 = RateLimiter(delay=0.72) # 720ms entre requests = 1.39 TPS
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 = requests.get(url, headers=self.headers)
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 = requests.get(url, headers=self.headers)
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
- response = requests.get(url, headers=self.headers, params=params)
203
-
204
- if response.status_code == 200:
205
- data = response.json()
206
- items = data.get('items', [])
207
-
208
- if not items:
209
- break
210
-
211
- all_results.extend(items)
212
-
213
- # Si hay menos items que per_page, es la última página
214
- if len(items) < per_page:
215
- break
216
-
217
- page += 1
218
- elif response.status_code == 403:
219
- # Rate limit alcanzado
220
- print(f"⚠️ Rate limit alcanzado. Resultados obtenidos: {len(all_results)}")
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
- else:
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
- Si no se proporciona, usa valores por defecto:
235
- - mobile: "bds_mobile"
236
- - web: "@bancolombia/design-system-web"
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
- response = requests.get(search_url, headers=self.headers, params=params)
310
-
311
- if response.status_code != 200:
312
- print(f"Error searching repositories: {response.status_code}")
313
- print(f"Response: {response.text}")
314
- break
315
-
316
- data = response.json()
317
- items = data.get('items', [])
318
-
319
- if not items:
320
- break
321
-
322
- all_results.extend(items)
323
-
324
- # Verificar si hay más páginas
325
- if len(items) < per_page:
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
- if self.enable_rate_limit:
343
- self._rate_limiter.wait()
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':
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: commons_metrics
3
- Version: 0.0.24
3
+ Version: 0.0.25
4
4
  Summary: A simple library for basic statistical calculations
5
5
  Author: Bancolombia
6
6
  Author-email: omar.david.pino@email.com
@@ -2,7 +2,7 @@ from setuptools import setup, find_packages
2
2
 
3
3
  setup(
4
4
  name='commons_metrics',
5
- version='0.0.24',
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',