strx-copy-installed-modules 0.1.1__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.
- strx_copy_installed_modules-0.1.1/PKG-INFO +9 -0
- strx_copy_installed_modules-0.1.1/README.md +30 -0
- strx_copy_installed_modules-0.1.1/setup.cfg +4 -0
- strx_copy_installed_modules-0.1.1/setup.py +20 -0
- strx_copy_installed_modules-0.1.1/src/strx_copy_installed_modules/__init__.py +0 -0
- strx_copy_installed_modules-0.1.1/src/strx_copy_installed_modules/main.py +364 -0
- strx_copy_installed_modules-0.1.1/src/strx_copy_installed_modules.egg-info/PKG-INFO +9 -0
- strx_copy_installed_modules-0.1.1/src/strx_copy_installed_modules.egg-info/SOURCES.txt +10 -0
- strx_copy_installed_modules-0.1.1/src/strx_copy_installed_modules.egg-info/dependency_links.txt +1 -0
- strx_copy_installed_modules-0.1.1/src/strx_copy_installed_modules.egg-info/entry_points.txt +2 -0
- strx_copy_installed_modules-0.1.1/src/strx_copy_installed_modules.egg-info/requires.txt +1 -0
- strx_copy_installed_modules-0.1.1/src/strx_copy_installed_modules.egg-info/top_level.txt +1 -0
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: strx-copy-installed-modules
|
|
3
|
+
Version: 0.1.1
|
|
4
|
+
Home-page: https://github.com/straconxsa/strx_tools/
|
|
5
|
+
Project-URL: Bug Tracker, https://github.com/straconxsa/strx_tools/issues
|
|
6
|
+
Requires-Dist: psycopg2-binary
|
|
7
|
+
Dynamic: home-page
|
|
8
|
+
Dynamic: project-url
|
|
9
|
+
Dynamic: requires-dist
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# STRX Copy Installed Modules
|
|
2
|
+
|
|
3
|
+
Herramienta para copiar m贸dulos instalados desde un directorio de origen (como initium16) a un directorio de destino bas谩ndose en el estado del m贸dulo en la base de datos de Odoo.
|
|
4
|
+
|
|
5
|
+
## 馃殌 Instalaci贸n
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
cd /odoo20/strx_tools/strx_copy_installed_modules
|
|
9
|
+
pip install -e .
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
## 馃幆 Uso
|
|
13
|
+
|
|
14
|
+
```bash
|
|
15
|
+
strx-copy-installed-modules --db_name NOMBRE_DB --destination /ruta/destino --initium_path /ruta/origen
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
### Opciones principales:
|
|
19
|
+
|
|
20
|
+
- `--db_name`: Nombre de la base de datos Odoo.
|
|
21
|
+
- `--destination`: Carpeta donde se copiar谩n los m贸dulos.
|
|
22
|
+
- `--initium_path`: Carpeta donde buscar los m贸dulos (por defecto `/odoo16/initium16`).
|
|
23
|
+
- `--preserve_structure`: Mantiene la estructura de carpetas original.
|
|
24
|
+
- `--db_user`: Usuario de la base de datos.
|
|
25
|
+
- `--db_passwd`: Contrase帽a de la base de datos.
|
|
26
|
+
- `--db_host`: Host de la base de datos (por defecto `localhost`).
|
|
27
|
+
- `--db_port`: Puerto de la base de datos (por defecto `5432`).
|
|
28
|
+
|
|
29
|
+
---
|
|
30
|
+
**Desarrollado por Initium Services ITS SAS**
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
from setuptools import setup, find_packages
|
|
2
|
+
|
|
3
|
+
setup(
|
|
4
|
+
name="strx-copy-installed-modules",
|
|
5
|
+
version="0.1.1",
|
|
6
|
+
url="https://github.com/straconxsa/strx_tools/",
|
|
7
|
+
project_urls={
|
|
8
|
+
"Bug Tracker": "https://github.com/straconxsa/strx_tools/issues",
|
|
9
|
+
},
|
|
10
|
+
package_dir={"": "src"},
|
|
11
|
+
packages=find_packages(where="src"),
|
|
12
|
+
install_requires=[
|
|
13
|
+
"psycopg2-binary",
|
|
14
|
+
],
|
|
15
|
+
entry_points={
|
|
16
|
+
"console_scripts": [
|
|
17
|
+
"strx-copy-installed-modules=strx_copy_installed_modules.main:main",
|
|
18
|
+
],
|
|
19
|
+
},
|
|
20
|
+
)
|
|
File without changes
|
|
@@ -0,0 +1,364 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
Script to copy installed modules from initium16 directory.
|
|
4
|
+
|
|
5
|
+
This script connects to an Odoo database, queries ir_module_module for modules
|
|
6
|
+
with state='installed', and copies those modules from the initium16 directory
|
|
7
|
+
to a specified destination directory.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
import os
|
|
11
|
+
import shutil
|
|
12
|
+
import psycopg2
|
|
13
|
+
import logging
|
|
14
|
+
import getpass
|
|
15
|
+
import configparser
|
|
16
|
+
import argparse
|
|
17
|
+
from pathlib import Path
|
|
18
|
+
from typing import List, Set, Optional
|
|
19
|
+
|
|
20
|
+
logging.basicConfig(
|
|
21
|
+
level=logging.INFO,
|
|
22
|
+
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
|
|
23
|
+
)
|
|
24
|
+
logger = logging.getLogger(__name__)
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def get_installed_modules(
|
|
28
|
+
db_user: str,
|
|
29
|
+
db_passwd: str,
|
|
30
|
+
db_host: str,
|
|
31
|
+
db_port: str,
|
|
32
|
+
db_name: str
|
|
33
|
+
) -> Set[str]:
|
|
34
|
+
"""
|
|
35
|
+
Get list of installed modules from ir_module_module table.
|
|
36
|
+
|
|
37
|
+
Args:
|
|
38
|
+
db_user: Database user
|
|
39
|
+
db_passwd: Database password
|
|
40
|
+
db_host: Database host
|
|
41
|
+
db_port: Database port
|
|
42
|
+
db_name: Database name
|
|
43
|
+
|
|
44
|
+
Returns:
|
|
45
|
+
Set of module technical names with state='installed'
|
|
46
|
+
"""
|
|
47
|
+
try:
|
|
48
|
+
conn = psycopg2.connect(
|
|
49
|
+
user=db_user,
|
|
50
|
+
password=db_passwd,
|
|
51
|
+
host=db_host,
|
|
52
|
+
port=db_port,
|
|
53
|
+
database=db_name
|
|
54
|
+
)
|
|
55
|
+
cursor = conn.cursor()
|
|
56
|
+
cursor.execute(
|
|
57
|
+
"SELECT name FROM ir_module_module WHERE state = 'installed'"
|
|
58
|
+
)
|
|
59
|
+
modules = {row[0] for row in cursor.fetchall()}
|
|
60
|
+
cursor.close()
|
|
61
|
+
conn.close()
|
|
62
|
+
logger.info(f"Found {len(modules)} installed modules in database")
|
|
63
|
+
return modules
|
|
64
|
+
except psycopg2.Error as e:
|
|
65
|
+
logger.error(f"Error connecting to database: {e}")
|
|
66
|
+
raise
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def find_module_in_initium(module_name: str, initium_path: str) -> Optional[Path]:
|
|
70
|
+
"""
|
|
71
|
+
Find module directory in initium16 path.
|
|
72
|
+
|
|
73
|
+
Args:
|
|
74
|
+
module_name: Technical name of the module
|
|
75
|
+
initium_path: Path to initium16 directory
|
|
76
|
+
|
|
77
|
+
Returns:
|
|
78
|
+
Path to module directory if found, None otherwise
|
|
79
|
+
"""
|
|
80
|
+
initium_path = Path(initium_path)
|
|
81
|
+
if not initium_path.exists():
|
|
82
|
+
logger.error(f"Initium path does not exist: {initium_path}")
|
|
83
|
+
return None
|
|
84
|
+
|
|
85
|
+
# Search in all subdirectories of initium16
|
|
86
|
+
for root, dirs, files in os.walk(initium_path):
|
|
87
|
+
# Skip extra directory
|
|
88
|
+
if 'extra' in root.split(os.sep):
|
|
89
|
+
continue
|
|
90
|
+
|
|
91
|
+
# Check if current directory is the module
|
|
92
|
+
current_dir = Path(root)
|
|
93
|
+
if current_dir.name == module_name:
|
|
94
|
+
# Verify it's a valid Odoo module (has __manifest__.py)
|
|
95
|
+
manifest_file = current_dir / '__manifest__.py'
|
|
96
|
+
if manifest_file.exists():
|
|
97
|
+
logger.debug(f"Found module {module_name} at {current_dir}")
|
|
98
|
+
return current_dir
|
|
99
|
+
|
|
100
|
+
logger.debug(f"Module {module_name} not found in {initium_path}")
|
|
101
|
+
return None
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
def copy_module(module_path: Path, destination_path: Path) -> bool:
|
|
105
|
+
"""
|
|
106
|
+
Copy module directory to destination.
|
|
107
|
+
|
|
108
|
+
Args:
|
|
109
|
+
module_path: Source path of the module
|
|
110
|
+
destination_path: Destination path where module will be copied
|
|
111
|
+
|
|
112
|
+
Returns:
|
|
113
|
+
True if copy was successful, False otherwise
|
|
114
|
+
"""
|
|
115
|
+
try:
|
|
116
|
+
if not module_path.exists():
|
|
117
|
+
logger.error(f"Module path does not exist: {module_path}")
|
|
118
|
+
return False
|
|
119
|
+
|
|
120
|
+
# Create destination directory if it doesn't exist
|
|
121
|
+
destination_path.parent.mkdir(parents=True, exist_ok=True)
|
|
122
|
+
|
|
123
|
+
# If destination already exists, remove it first
|
|
124
|
+
if destination_path.exists():
|
|
125
|
+
logger.warning(
|
|
126
|
+
f"Destination {destination_path} already exists. Removing it."
|
|
127
|
+
)
|
|
128
|
+
shutil.rmtree(destination_path)
|
|
129
|
+
|
|
130
|
+
# Copy the entire module directory
|
|
131
|
+
shutil.copytree(module_path, destination_path)
|
|
132
|
+
logger.info(f"Copied {module_path.name} to {destination_path}")
|
|
133
|
+
return True
|
|
134
|
+
except Exception as e:
|
|
135
|
+
logger.error(f"Error copying module {module_path}: {e}")
|
|
136
|
+
return False
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
def copy_installed_modules(
|
|
140
|
+
db_user: str,
|
|
141
|
+
db_passwd: str,
|
|
142
|
+
db_host: str,
|
|
143
|
+
db_port: str,
|
|
144
|
+
db_name: str,
|
|
145
|
+
initium_path: str,
|
|
146
|
+
destination_path: str,
|
|
147
|
+
preserve_structure: bool = False
|
|
148
|
+
) -> dict:
|
|
149
|
+
"""
|
|
150
|
+
Main function to copy installed modules from initium16.
|
|
151
|
+
|
|
152
|
+
Args:
|
|
153
|
+
db_user: Database user
|
|
154
|
+
db_passwd: Database password
|
|
155
|
+
db_host: Database host
|
|
156
|
+
db_port: Database port
|
|
157
|
+
db_name: Database name
|
|
158
|
+
initium_path: Path to initium16 directory
|
|
159
|
+
destination_path: Path where modules will be copied
|
|
160
|
+
preserve_structure: If True, preserve directory structure from initium16
|
|
161
|
+
|
|
162
|
+
Returns:
|
|
163
|
+
Dictionary with statistics about the copy operation
|
|
164
|
+
"""
|
|
165
|
+
stats = {
|
|
166
|
+
'total_installed': 0,
|
|
167
|
+
'found_in_initium': 0,
|
|
168
|
+
'copied': 0,
|
|
169
|
+
'failed': 0,
|
|
170
|
+
'not_found': []
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
# Get installed modules from database
|
|
174
|
+
logger.info("Querying database for installed modules...")
|
|
175
|
+
installed_modules = get_installed_modules(
|
|
176
|
+
db_user, db_passwd, db_host, db_port, db_name
|
|
177
|
+
)
|
|
178
|
+
stats['total_installed'] = len(installed_modules)
|
|
179
|
+
|
|
180
|
+
# Find and copy modules
|
|
181
|
+
destination_base = Path(destination_path)
|
|
182
|
+
destination_base.mkdir(parents=True, exist_ok=True)
|
|
183
|
+
|
|
184
|
+
for module_name in sorted(installed_modules):
|
|
185
|
+
logger.info(f"Processing module: {module_name}")
|
|
186
|
+
module_path = find_module_in_initium(module_name, initium_path)
|
|
187
|
+
|
|
188
|
+
if not module_path:
|
|
189
|
+
stats['not_found'].append(module_name)
|
|
190
|
+
logger.warning(f"Module {module_name} not found in {initium_path}")
|
|
191
|
+
continue
|
|
192
|
+
|
|
193
|
+
stats['found_in_initium'] += 1
|
|
194
|
+
|
|
195
|
+
# Determine destination path
|
|
196
|
+
if preserve_structure:
|
|
197
|
+
# Preserve relative structure from initium16
|
|
198
|
+
relative_path = module_path.relative_to(Path(initium_path))
|
|
199
|
+
dest_path = destination_base / relative_path
|
|
200
|
+
else:
|
|
201
|
+
# Copy all modules to flat structure
|
|
202
|
+
dest_path = destination_base / module_name
|
|
203
|
+
|
|
204
|
+
# Copy module
|
|
205
|
+
if copy_module(module_path, dest_path):
|
|
206
|
+
stats['copied'] += 1
|
|
207
|
+
else:
|
|
208
|
+
stats['failed'] += 1
|
|
209
|
+
|
|
210
|
+
return stats
|
|
211
|
+
|
|
212
|
+
|
|
213
|
+
def print_statistics(stats: dict):
|
|
214
|
+
"""
|
|
215
|
+
Print copy operation statistics.
|
|
216
|
+
|
|
217
|
+
Args:
|
|
218
|
+
stats: Dictionary with statistics
|
|
219
|
+
"""
|
|
220
|
+
print("\n" + "=" * 60)
|
|
221
|
+
print("COPY OPERATION STATISTICS")
|
|
222
|
+
print("=" * 60)
|
|
223
|
+
print(f"Total installed modules in database: {stats['total_installed']}")
|
|
224
|
+
print(f"Modules found in initium16: {stats['found_in_initium']}")
|
|
225
|
+
print(f"Modules successfully copied: {stats['copied']}")
|
|
226
|
+
print(f"Modules failed to copy: {stats['failed']}")
|
|
227
|
+
print(f"Modules not found in initium16: {len(stats['not_found'])}")
|
|
228
|
+
print("=" * 60)
|
|
229
|
+
|
|
230
|
+
if stats['not_found']:
|
|
231
|
+
print("\nModules not found in initium16:")
|
|
232
|
+
for module in sorted(stats['not_found']):
|
|
233
|
+
print(f" - {module}")
|
|
234
|
+
|
|
235
|
+
|
|
236
|
+
def main():
|
|
237
|
+
"""Main entry point for the script."""
|
|
238
|
+
parser = argparse.ArgumentParser(
|
|
239
|
+
description="Copy installed modules from initium16 directory"
|
|
240
|
+
)
|
|
241
|
+
parser.add_argument(
|
|
242
|
+
"--db_name",
|
|
243
|
+
required=True,
|
|
244
|
+
help="Database name"
|
|
245
|
+
)
|
|
246
|
+
parser.add_argument(
|
|
247
|
+
"--db_user",
|
|
248
|
+
help="Database user"
|
|
249
|
+
)
|
|
250
|
+
parser.add_argument(
|
|
251
|
+
"--db_passwd",
|
|
252
|
+
help="Database password"
|
|
253
|
+
)
|
|
254
|
+
parser.add_argument(
|
|
255
|
+
"--db_host",
|
|
256
|
+
default="localhost",
|
|
257
|
+
help="Database host (default: localhost)"
|
|
258
|
+
)
|
|
259
|
+
parser.add_argument(
|
|
260
|
+
"--db_port",
|
|
261
|
+
default="5432",
|
|
262
|
+
help="Database port (default: 5432)"
|
|
263
|
+
)
|
|
264
|
+
parser.add_argument(
|
|
265
|
+
"--initium_path",
|
|
266
|
+
default="/odoo16/initium16",
|
|
267
|
+
help="Path to initium16 directory (default: /odoo16/initium16)"
|
|
268
|
+
)
|
|
269
|
+
parser.add_argument(
|
|
270
|
+
"--destination",
|
|
271
|
+
required=True,
|
|
272
|
+
help="Destination directory where modules will be copied"
|
|
273
|
+
)
|
|
274
|
+
parser.add_argument(
|
|
275
|
+
"--preserve_structure",
|
|
276
|
+
action="store_true",
|
|
277
|
+
help="Preserve directory structure from initium16"
|
|
278
|
+
)
|
|
279
|
+
parser.add_argument(
|
|
280
|
+
"--config",
|
|
281
|
+
default="odoo_config.conf",
|
|
282
|
+
help="Configuration file path (default: odoo_config.conf)"
|
|
283
|
+
)
|
|
284
|
+
parser.add_argument(
|
|
285
|
+
"--verbose",
|
|
286
|
+
"-v",
|
|
287
|
+
action="store_true",
|
|
288
|
+
help="Enable verbose logging"
|
|
289
|
+
)
|
|
290
|
+
|
|
291
|
+
args = parser.parse_args()
|
|
292
|
+
|
|
293
|
+
# Set logging level
|
|
294
|
+
if args.verbose:
|
|
295
|
+
logging.getLogger().setLevel(logging.DEBUG)
|
|
296
|
+
|
|
297
|
+
# Load configuration from file if exists
|
|
298
|
+
db_config = {}
|
|
299
|
+
config_file = (
|
|
300
|
+
args.config
|
|
301
|
+
if os.path.isabs(args.config)
|
|
302
|
+
else os.path.join(os.path.dirname(__file__), args.config)
|
|
303
|
+
)
|
|
304
|
+
|
|
305
|
+
if os.path.exists(config_file):
|
|
306
|
+
logger.info(f"Loading configuration from {config_file}")
|
|
307
|
+
config = configparser.ConfigParser()
|
|
308
|
+
config.read(config_file)
|
|
309
|
+
if 'database' in config:
|
|
310
|
+
db_config = config['database']
|
|
311
|
+
|
|
312
|
+
# Get database credentials
|
|
313
|
+
db_user = args.db_user or db_config.get('db_user')
|
|
314
|
+
db_passwd = args.db_passwd or db_config.get('db_passwd')
|
|
315
|
+
db_host = args.db_host or db_config.get('db_host', 'localhost')
|
|
316
|
+
db_port = args.db_port or db_config.get('db_port', '5432')
|
|
317
|
+
|
|
318
|
+
# Prompt for password if not provided
|
|
319
|
+
if not db_passwd:
|
|
320
|
+
db_passwd = getpass.getpass("Enter database password: ")
|
|
321
|
+
|
|
322
|
+
# Validate required parameters
|
|
323
|
+
if not db_user:
|
|
324
|
+
logger.error("Database user is required. Provide --db_user or set in config file.")
|
|
325
|
+
return 1
|
|
326
|
+
|
|
327
|
+
if not args.db_name:
|
|
328
|
+
logger.error("Database name is required. Provide --db_name.")
|
|
329
|
+
return 1
|
|
330
|
+
|
|
331
|
+
if not args.destination:
|
|
332
|
+
logger.error("Destination path is required. Provide --destination.")
|
|
333
|
+
return 1
|
|
334
|
+
|
|
335
|
+
# Validate paths
|
|
336
|
+
if not os.path.exists(args.initium_path):
|
|
337
|
+
logger.error(f"Initium path does not exist: {args.initium_path}")
|
|
338
|
+
return 1
|
|
339
|
+
|
|
340
|
+
# Execute copy operation
|
|
341
|
+
try:
|
|
342
|
+
stats = copy_installed_modules(
|
|
343
|
+
db_user=db_user,
|
|
344
|
+
db_passwd=db_passwd,
|
|
345
|
+
db_host=db_host,
|
|
346
|
+
db_port=db_port,
|
|
347
|
+
db_name=args.db_name,
|
|
348
|
+
initium_path=args.initium_path,
|
|
349
|
+
destination_path=args.destination,
|
|
350
|
+
preserve_structure=args.preserve_structure
|
|
351
|
+
)
|
|
352
|
+
|
|
353
|
+
print_statistics(stats)
|
|
354
|
+
return 0
|
|
355
|
+
|
|
356
|
+
except Exception as e:
|
|
357
|
+
logger.error(f"Error during copy operation: {e}")
|
|
358
|
+
logger.exception(e)
|
|
359
|
+
return 1
|
|
360
|
+
|
|
361
|
+
|
|
362
|
+
if __name__ == "__main__":
|
|
363
|
+
exit(main())
|
|
364
|
+
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: strx-copy-installed-modules
|
|
3
|
+
Version: 0.1.1
|
|
4
|
+
Home-page: https://github.com/straconxsa/strx_tools/
|
|
5
|
+
Project-URL: Bug Tracker, https://github.com/straconxsa/strx_tools/issues
|
|
6
|
+
Requires-Dist: psycopg2-binary
|
|
7
|
+
Dynamic: home-page
|
|
8
|
+
Dynamic: project-url
|
|
9
|
+
Dynamic: requires-dist
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
README.md
|
|
2
|
+
setup.py
|
|
3
|
+
src/strx_copy_installed_modules/__init__.py
|
|
4
|
+
src/strx_copy_installed_modules/main.py
|
|
5
|
+
src/strx_copy_installed_modules.egg-info/PKG-INFO
|
|
6
|
+
src/strx_copy_installed_modules.egg-info/SOURCES.txt
|
|
7
|
+
src/strx_copy_installed_modules.egg-info/dependency_links.txt
|
|
8
|
+
src/strx_copy_installed_modules.egg-info/entry_points.txt
|
|
9
|
+
src/strx_copy_installed_modules.egg-info/requires.txt
|
|
10
|
+
src/strx_copy_installed_modules.egg-info/top_level.txt
|
strx_copy_installed_modules-0.1.1/src/strx_copy_installed_modules.egg-info/dependency_links.txt
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
psycopg2-binary
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
strx_copy_installed_modules
|