paramsx 1.0.0__py3-none-any.whl

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.

Potentially problematic release.


This version of paramsx might be problematic. Click here for more details.

paramsx/__init__.py ADDED
File without changes
paramsx/funcions.py ADDED
@@ -0,0 +1,254 @@
1
+ import time
2
+ import os
3
+ from .text import HEADER_ASCII, FOOTER_TEXT
4
+ import curses
5
+
6
+ # Función para obtener parámetros con un entorno específico
7
+ def get_parameters_by_prefix(ssm, prefix):
8
+ parameters = []
9
+ next_token = None
10
+
11
+ while True:
12
+ request_args = {
13
+ "Path": prefix,
14
+ "WithDecryption": True,
15
+ "Recursive": True,
16
+ }
17
+
18
+ if next_token:
19
+ request_args["NextToken"] = next_token
20
+
21
+ try:
22
+ response = ssm.get_parameters_by_path(**request_args)
23
+ except Exception as e:
24
+ raise RuntimeError(f"Error al obtener parámetros: {e}")
25
+
26
+ for param in response.get('Parameters', []):
27
+ parameters.append({
28
+ "parameter_name": param['Name'],
29
+ "parameter_value": param['Value']
30
+ })
31
+
32
+ next_token = response.get('NextToken')
33
+ if not next_token:
34
+ break
35
+
36
+ if not parameters:
37
+ raise ValueError(f"No se encontraron parámetros con el prefijo: {prefix}")
38
+
39
+ return parameters
40
+
41
+ # Función para borrar los parámetros
42
+ def delete_parameter(ssm_client, parameter_name):
43
+ try:
44
+ ssm_client.delete_parameter(Name=parameter_name)
45
+ print(f"Parámetro eliminado: {parameter_name}")
46
+ except Exception as e:
47
+ print(f"Error al eliminar el parámetro {parameter_name}: {e}")
48
+
49
+ # Función para exportar parámetros a un archivo
50
+ def export_parameters_to_file(parameters, file_path):
51
+ with open(file_path, 'w') as f:
52
+ f.write("# PARAMS exportados\n")
53
+ f.write("parametros = [\n")
54
+ for param in parameters:
55
+ parameter_name = param['parameter_name']
56
+ parameter_value = param['parameter_value']
57
+ # Usar triple comillas para valores largos
58
+ f.write(f" {{'parameter_name': '{parameter_name}',\n")
59
+ f.write(f" 'parameter_value': \"\"\"{parameter_value}\"\"\"}},\n\n")
60
+ f.write("]\n")
61
+
62
+ # Función para comparar parámetros y mostrar las diferencias
63
+ def compare_parameters(file_path, backup_file_path, stdscr):
64
+ if not os.path.exists(file_path) or not os.path.exists(backup_file_path):
65
+ return None
66
+
67
+ # Usar diccionarios específicos para cargar los archivos
68
+ current_scope = {}
69
+ backup_scope = {}
70
+
71
+ with open(file_path, 'r') as f:
72
+ exec(f.read(), current_scope)
73
+
74
+ with open(backup_file_path, 'r') as f:
75
+ exec(f.read(), backup_scope)
76
+
77
+ # Extraer los parámetros de cada archivo
78
+ current_params = current_scope.get('parametros', [])
79
+ backup_params = backup_scope.get('parametros', [])
80
+
81
+ # Comparar parámetros
82
+ changes = []
83
+ current_dict = {p['parameter_name']: p['parameter_value'] for p in current_params}
84
+ backup_dict = {p['parameter_name']: p['parameter_value'] for p in backup_params}
85
+
86
+ for key in current_dict:
87
+ if key not in backup_dict:
88
+ changes.append((key, "Nuevo", f"{key} - Nuevo"))
89
+ elif current_dict[key] != backup_dict[key]:
90
+ changes.append((key, "Modificado", f"{key} - Modificado"))
91
+
92
+ for key in backup_dict:
93
+ if key not in current_dict:
94
+ changes.append((key, "Eliminado", f"{key} - Eliminado"))
95
+
96
+ return changes
97
+
98
+ # Mostrar contenido comparado
99
+ def show_comparison_results(stdscr, changes):
100
+ stdscr.clear()
101
+ draw_header(stdscr)
102
+
103
+ max_y, max_x = stdscr.getmaxyx() # Tamaño del terminal
104
+ start_line = HEADER_ASCII.count("\n") + 1 # Reservar espacio para el header
105
+ visible_height = max_y - start_line - 3 # Espacio visible en el terminal (sin header/footer)
106
+ scroll_offset = 0 # Controlar desde qué línea se empieza a mostrar
107
+ buffer_lines = 2 # Número de líneas adicionales para mostrar al final
108
+
109
+ while True:
110
+ stdscr.clear()
111
+ draw_header(stdscr)
112
+
113
+ # Mostrar encabezado de la lista con cantidad y guía de navegación
114
+ title = f"Cambios detectados ({len(changes)}) - Usa ↑ y ↓ para navegar - Intro confirmar y Esc para salir"
115
+ stdscr.addstr(start_line, 0, title.center(max_x, "-"))
116
+
117
+ # Calcular el rango visible incluyendo el buffer
118
+ visible_changes = changes[scroll_offset:scroll_offset + visible_height]
119
+
120
+ for i, change in enumerate(visible_changes, start=1 + scroll_offset):
121
+ comment = f"{i}. {change[0]} - {change[1]}"
122
+ truncated_comment = comment[:max_x - 1] # Truncar si excede el ancho
123
+ stdscr.addstr(start_line + i - scroll_offset, 0, truncated_comment)
124
+
125
+ # Mostrar footer vacío o decorativo
126
+ draw_footer(stdscr)
127
+
128
+ # Actualizar pantalla
129
+ stdscr.refresh()
130
+
131
+ # Capturar entrada del usuario
132
+ key = stdscr.getkey()
133
+
134
+ # Manejar teclas de desplazamiento
135
+ if key == "KEY_UP" and scroll_offset > 0:
136
+ scroll_offset -= 1
137
+ elif key == "KEY_DOWN" and scroll_offset + visible_height < len(changes) + buffer_lines:
138
+ scroll_offset += 1
139
+ elif key == '\x1b': # Presionar Esc para salir
140
+ return False
141
+ elif key == '\n': # Presionar Enter para confirmar
142
+ return True
143
+
144
+
145
+ # Función para mostrar el header personalizado
146
+ def draw_header(stdscr):
147
+ max_y, max_x = stdscr.getmaxyx()
148
+ separator = "=" * max_x
149
+ stdscr.addstr(0, 0, separator) # Línea superior del header
150
+
151
+ # Mostrar HEADER_ASCII línea por línea sin strip
152
+ header_lines = HEADER_ASCII.split("\n") # No eliminar espacios ni líneas vacías
153
+ last_line_index = 0 # Guardar el índice de la última línea válida
154
+
155
+ for i, line in enumerate(header_lines, start=1):
156
+ if line.strip(): # Solo imprimir líneas no vacías
157
+ stdscr.addstr(i, 0, line)
158
+ last_line_index = i # Actualizar la posición de la última línea visible
159
+
160
+ stdscr.addstr(last_line_index + 1, 0, separator) # Línea inferior después de la última línea del header
161
+
162
+ # Función para mostrar el footer personalizado
163
+ def draw_footer(stdscr):
164
+ rows, cols = stdscr.getmaxyx() # Tamaño del terminal
165
+ exit_text = "Pulsa 'Esc' para volver o salir" # Mensaje adicional
166
+ footer_text = FOOTER_TEXT[:cols - 1] # Recortar el texto si es más ancho que el terminal
167
+ separator = "-" * cols # Separador completo ajustado al ancho
168
+
169
+ # Asegurarse de que hay espacio suficiente para el footer
170
+ if rows > 2: # Verifica que haya espacio mínimo para el footer
171
+ try:
172
+ stdscr.addstr(rows - 3, 0, exit_text.center(cols, " ")) # Centrar el texto de salida
173
+ stdscr.addstr(rows - 2, 0, separator) # Línea separadora
174
+ stdscr.addstr(rows - 1, 0, footer_text.center(cols - 1, " ")) # Centrar el texto
175
+ except curses.error:
176
+ pass # Ignorar errores si no hay espacio suficiente
177
+
178
+ # Mostrar contenido principal del menú
179
+ def show_main_menu(stdscr):
180
+ start_line = HEADER_ASCII.count("\n") + 1 # Calcula dónde termina el header
181
+ stdscr.addstr(start_line, 0, "1. Leer parámetros".ljust(60))
182
+ stdscr.addstr(start_line + 1, 0, "2. Cargar parámetros desde archivo".ljust(60))
183
+ stdscr.addstr(start_line + 2, 0, "3. Crear Backup de parámetros".ljust(60))
184
+ stdscr.addstr(start_line + 3, 0, "Elija una opción (1/2): ")
185
+
186
+ # Mostrar selección de parámetros
187
+ def show_parameter_selection(stdscr, options):
188
+ stdscr.clear()
189
+ draw_header(stdscr)
190
+ start_line = HEADER_ASCII.count("\n") + 1 # Calcula el inicio después del header
191
+ stdscr.addstr(start_line, 0, "Seleccione un parámetro para leer:".center(60, "-"))
192
+ for idx, option in enumerate(options, start=1):
193
+ stdscr.addstr(start_line + idx, 0, f"{idx}. {option}")
194
+ draw_footer(stdscr)
195
+ stdscr.refresh()
196
+
197
+ while True:
198
+ key = stdscr.getkey()
199
+ if key == '\x1b': # Si se presiona Esc
200
+ return None # Cancelar selección
201
+ try:
202
+ choice = int(key) - 1
203
+ if 0 <= choice < len(options):
204
+ return choice
205
+ except ValueError:
206
+ pass # Ignorar entradas inválidas
207
+
208
+ # Mostrar selección de entorno
209
+ def show_environment_selection(stdscr, environments):
210
+ stdscr.clear()
211
+ draw_header(stdscr)
212
+ start_line = HEADER_ASCII.count("\n") + 1
213
+ stdscr.addstr(start_line, 0, "Seleccione el entorno:".center(60, "-"))
214
+ for idx, env in enumerate(environments, start=1):
215
+ stdscr.addstr(start_line + idx, 0, f"{idx}. {env}")
216
+ draw_footer(stdscr)
217
+ stdscr.refresh()
218
+
219
+ while True:
220
+ key = stdscr.getkey()
221
+ if key == '\x1b': # Si se presiona Esc
222
+ return None # Cancelar selección
223
+ try:
224
+ choice = int(key) - 1
225
+ if 0 <= choice < len(environments):
226
+ return choice
227
+ except ValueError:
228
+ pass # Ignorar entradas inválidas
229
+
230
+ # Pantalla de confirmación o resultados
231
+ def show_message(stdscr, message, color_pair=1):
232
+ stdscr.clear()
233
+ draw_header(stdscr)
234
+ start_line = HEADER_ASCII.count("\n") + 1
235
+ stdscr.addstr(start_line, 0, message.center(60, " "), curses.color_pair(color_pair))
236
+ draw_footer(stdscr)
237
+ stdscr.refresh()
238
+ time.sleep(3)
239
+
240
+ # Errores varios
241
+ def load_parameters(file_path):
242
+ if not os.path.exists(file_path):
243
+ raise FileNotFoundError(f"El archivo {file_path} no existe.")
244
+
245
+ param_scope = {}
246
+ try:
247
+ with open(file_path, 'r') as f:
248
+ exec(f.read(), param_scope) # Ejecuta el archivo en el contexto de param_scope
249
+ except SyntaxError as e:
250
+ raise SyntaxError(
251
+ f"Error de sintaxis en {file_path}.\nRevisa las comillas o el formato del archivo."
252
+ )
253
+
254
+ return param_scope.get('parametros', [])
paramsx/main.py ADDED
@@ -0,0 +1,206 @@
1
+ import os
2
+ import boto3
3
+ import curses
4
+ import importlib.util
5
+ from .funcions import (
6
+ draw_header, draw_footer, show_main_menu, show_comparison_results,
7
+ show_environment_selection, show_message, show_parameter_selection,
8
+ get_parameters_by_prefix, delete_parameter, export_parameters_to_file,
9
+ compare_parameters, load_parameters
10
+ )
11
+
12
+
13
+ # Ruta de la configuración personalizada
14
+ CONFIG_PATH = os.path.expanduser("~/.xsoft/paramsx_config.py")
15
+
16
+ # Cargar configuraciones desde el archivo de usuario
17
+ def load_config():
18
+ if not os.path.exists(CONFIG_PATH):
19
+ raise FileNotFoundError(f"No se encontró el archivo de configuración en {CONFIG_PATH}")
20
+ spec = importlib.util.spec_from_file_location("config", CONFIG_PATH)
21
+ config = importlib.util.module_from_spec(spec)
22
+ spec.loader.exec_module(config)
23
+ return config.configuraciones
24
+
25
+ # Función principal
26
+ def main(stdscr):
27
+
28
+ ## Cargando configuración
29
+ config = load_config()
30
+ # Configurar boto3 con el perfil y región del usuario
31
+ boto3.setup_default_session(profile_name=config["profile_name"])
32
+ ssm = boto3.client("ssm", region_name=config["region_name"])
33
+
34
+ curses.start_color()
35
+ curses.init_pair(1, curses.COLOR_WHITE, curses.COLOR_BLUE)
36
+ curses.init_pair(2, curses.COLOR_RED, curses.COLOR_BLACK)
37
+ curses.init_pair(3, curses.COLOR_GREEN, curses.COLOR_BLACK)
38
+
39
+ environments = config['entornos']
40
+ PARAMETER_LIST = config['parameter_list']
41
+
42
+ while True:
43
+ stdscr.clear()
44
+ draw_header(stdscr)
45
+ show_main_menu(stdscr)
46
+ draw_footer(stdscr)
47
+ stdscr.refresh()
48
+
49
+ choice = stdscr.getkey()
50
+
51
+ if choice == '\x1b': # Código ASCII para 'Esc'
52
+ break # Salir del programa
53
+
54
+ elif choice == '1':
55
+ # Leer parámetros
56
+ param_choice = show_parameter_selection(stdscr, PARAMETER_LIST)
57
+ if param_choice is None: # Si se presionó Esc
58
+ continue # Regresar al menú principal
59
+
60
+ selected_param = PARAMETER_LIST[param_choice]
61
+
62
+ env_choice = show_environment_selection(stdscr, environments)
63
+ if env_choice is None: # Si se presionó Esc
64
+ continue # Regresar al menú principal
65
+
66
+ selected_env = environments[env_choice]
67
+
68
+ # Crear el prefijo completo
69
+ full_path = f"{selected_param}/{selected_env}"
70
+ show_message(stdscr, f"Buscando parámetros en: {full_path}...", 3) # Mensaje inicial
71
+
72
+ try:
73
+ # Obtener parámetros desde AWS SSM
74
+ parameters = get_parameters_by_prefix(ssm, full_path)
75
+ except ValueError as e:
76
+ show_message(stdscr, f"ERROR: {e}", 2) # Error por prefijo vacío
77
+ continue # Regresar al menú principal
78
+ except Exception as e:
79
+ show_message(stdscr, f"Error inesperado: {e}", 2) # Error genérico
80
+ continue # Regresar al menú principal
81
+
82
+ # Validar si se encontraron parámetros antes de continuar
83
+ if not parameters:
84
+ show_message(stdscr, f"No se encontraron parámetros en: {full_path}.", 2)
85
+ continue # Regresar al menú principal
86
+
87
+ # Crear archivos si se encontraron parámetros
88
+ file_name = f"parameters_{selected_env}.py"
89
+ backup_file_name = f"parameters_{selected_env}_backup.py"
90
+
91
+ # Exportar parámetros al archivo principal
92
+ export_parameters_to_file(parameters, file_name)
93
+
94
+ # Crear un respaldo exacto del archivo principal
95
+ export_parameters_to_file(parameters, backup_file_name)
96
+
97
+ # Confirmación de archivos creados
98
+ show_message(stdscr, f"Archivos creados:\n- {file_name}\n- {backup_file_name}", 3)
99
+
100
+ elif choice == '2':
101
+ # Cargar parámetros desde archivo
102
+ env_choice = show_environment_selection(stdscr, environments)
103
+ if env_choice is None: # Si se presionó Esc
104
+ continue # Regresar al menú principal
105
+
106
+ selected_env = environments[env_choice]
107
+ file_name = f"parameters_{selected_env}.py"
108
+ backup_file_name = f"parameters_{selected_env}_backup.py"
109
+
110
+ if not os.path.exists(file_name) or not os.path.exists(backup_file_name):
111
+ show_message(stdscr, f"Archivos {file_name} o {backup_file_name} no encontrados.", 2)
112
+ continue
113
+
114
+
115
+ try:
116
+ # Cargar parámetros del archivo principal
117
+ parametros = load_parameters(file_name)
118
+ except SyntaxError as e:
119
+ show_message(stdscr, f"ERROR: {e}", 2)
120
+ continue # Regresar al menú principal
121
+ except Exception as e:
122
+ show_message(stdscr, f"Error inesperado: {e}", 2)
123
+ continue # Regresar al menú principal
124
+
125
+ # Comparar los parámetros
126
+ changes = compare_parameters(file_name, backup_file_name, stdscr)
127
+
128
+ if changes:
129
+ # Mostrar resultados de la comparación
130
+ confirmed = show_comparison_results(stdscr, changes)
131
+
132
+ if not confirmed: # Si el usuario cancela
133
+ show_message(stdscr, "Operación cancelada volvemos a menú principal.", 2)
134
+ continue
135
+
136
+ # Subir parámetros si se confirman los cambios
137
+ with open(file_name, 'r') as f:
138
+ param_scope = {}
139
+ exec(f.read(), param_scope)
140
+
141
+ parametros = param_scope.get('parametros', [])
142
+ current_dict = {p['parameter_name']: p['parameter_value'] for p in parametros}
143
+
144
+ for change in changes:
145
+ param_name, change_type, _ = change
146
+ if change_type == "Nuevo" or change_type == "Modificado":
147
+ # Subir o actualizar parámetros
148
+ ssm.put_parameter(
149
+ Name=param_name,
150
+ Value=current_dict[param_name],
151
+ Type='SecureString',
152
+ Overwrite=True
153
+ )
154
+ elif change_type == "Eliminado":
155
+ # Borrar parámetros eliminados
156
+ delete_parameter(ssm, param_name)
157
+
158
+ # Eliminar los archivos una vez procesados
159
+ os.remove(file_name)
160
+ os.remove(backup_file_name)
161
+ show_message(stdscr, "¡Cambios aplicados y archivos eliminados!", 3)
162
+ else:
163
+ show_message(stdscr, "No se encontraron cambios entre los archivos.", 3)
164
+
165
+ elif choice == '3':
166
+ # Crear backup
167
+ param_choice = show_parameter_selection(stdscr, PARAMETER_LIST)
168
+ if param_choice is None: # Si se presionó Esc
169
+ continue # Regresar al menú principal
170
+
171
+ selected_param = PARAMETER_LIST[param_choice]
172
+
173
+ env_choice = show_environment_selection(stdscr, environments)
174
+ if env_choice is None: # Si se presionó Esc
175
+ continue # Regresar al menú principal
176
+
177
+ selected_env = environments[env_choice]
178
+
179
+ # Crear el prefijo completo
180
+ full_path = f"{selected_param}/{selected_env}"
181
+ try:
182
+ # Obtener parámetros desde AWS SSM
183
+ parameters = get_parameters_by_prefix(ssm, full_path)
184
+ except ValueError as e:
185
+ show_message(stdscr, f"ERROR: {e}", 2)
186
+ continue # Regresar al menú principal
187
+ except Exception as e:
188
+ show_message(stdscr, f"Error inesperado: {e}", 2)
189
+ continue # Regresar al menú principal
190
+
191
+ # Crear archivo de backup con nombre claro
192
+ backup_file_name = f"{selected_param.replace('/', '_')}_{selected_env}_backup.py"
193
+ export_parameters_to_file(parameters, backup_file_name)
194
+
195
+ # Confirmación de backup creado
196
+ show_message(stdscr, f"Backup creado:\n- {backup_file_name}", 3)
197
+
198
+
199
+ else:
200
+ show_message(stdscr, "Opción inválida. Inténtalo de nuevo.", 2)
201
+
202
+ def entry_point():
203
+ curses.wrapper(main)
204
+
205
+ if __name__ == "__main__":
206
+ curses.wrapper(main)
@@ -0,0 +1,12 @@
1
+ ## Configuraciones ParamsX
2
+
3
+ configuraciones = {
4
+ "profile_name": "default",
5
+ "region_name": "eu-south-2",
6
+ "entornos": ['DEV','PROD'],
7
+ "parameter_list":[
8
+ "params1",
9
+ "params2"
10
+ ]
11
+ }
12
+
paramsx/text.py ADDED
@@ -0,0 +1,12 @@
1
+ HEADER_ASCII = """
2
+ _____ __ __
3
+ | __ \ \ \ / /
4
+ | |__) |_ _ _ __ __ _ _ __ ___ ___ \ V /
5
+ | ___/ _` | '__/ _` | '_ ` _ \/ __| > <
6
+ | | | (_| | | | (_| | | | | | \__ \/ . \
7
+ |_| \__,_|_| \__,_|_| |_| |_|___/_/ \_\
8
+
9
+
10
+ """
11
+
12
+ FOOTER_TEXT = "By Mariox"
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2024 Mario x
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,9 @@
1
+ Metadata-Version: 2.1
2
+ Name: paramsx
3
+ Version: 1.0.0
4
+ Summary: Librería para gestionar y respaldar parámetros de AWS SSM de manera sencilla y eficiente.
5
+ Home-page: https://github.com/pistatxos/paramsx
6
+ Author: Mariox
7
+ Author-email: info@tomonota.net
8
+ License-File: LICENSE
9
+ Requires-Dist: boto3
@@ -0,0 +1,11 @@
1
+ paramsx/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
2
+ paramsx/funcions.py,sha256=1053brCERgadfA1lB31U8VbnYksCbfttQ5HnxhDlZ2Q,9629
3
+ paramsx/main.py,sha256=Wgh-8bibDIGhdeoTcRCZr39ph_D0AYnQeODCjmScAjQ,8356
4
+ paramsx/paramsx_config.py,sha256=uBKYtCLiPfuCw58nzMCc-kKx2yXJLXA4ytaXHOneP74,213
5
+ paramsx/text.py,sha256=nJf5wJ0dZNiuBrp8zFacu-mWe1FbIEcslWRokR9SPS4,315
6
+ paramsx-1.0.0.dist-info/LICENSE,sha256=GrUwVxQBOoDjqiy-ts0ms4stmdZjDEZZlkjUgCaGcCQ,1064
7
+ paramsx-1.0.0.dist-info/METADATA,sha256=rYC2-6_DBNiyupV-D8vl8ZYF5vxYqkhusUeSEVanH0s,290
8
+ paramsx-1.0.0.dist-info/WHEEL,sha256=PZUExdf71Ui_so67QXpySuHtCi3-J3wvF4ORK6k_S8U,91
9
+ paramsx-1.0.0.dist-info/entry_points.txt,sha256=DaoL0NejfYIz3DaYsnAZzB3pWhtqydt-T5K2rE4iQ1k,53
10
+ paramsx-1.0.0.dist-info/top_level.txt,sha256=LiSJIPKNZVfaJcRHqKr0YoBl7cHS_IolBI6cYB-aBbE,8
11
+ paramsx-1.0.0.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (75.6.0)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ paramsx = paramsx.main:entry_point
@@ -0,0 +1 @@
1
+ paramsx