sdv-installer 0.0.0.dev11__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.
@@ -0,0 +1,490 @@
1
+ """Installer of SDV-Enterprise."""
2
+
3
+ import glob
4
+ import json
5
+ import logging
6
+ import subprocess
7
+ import sys
8
+ from typing import Dict, List, Optional, Union
9
+ from urllib.parse import quote_plus
10
+
11
+ import requests
12
+ from requests.exceptions import RequestException
13
+
14
+ from sdv_installer import config
15
+ from sdv_installer.authentication import authenticate
16
+ from sdv_installer.constants import (
17
+ CONNECTORS_PACKAGES,
18
+ EXPECTED_PACKAGES,
19
+ TRUSTED_HOSTS,
20
+ ExitCode,
21
+ ProductType,
22
+ )
23
+ from sdv_installer.utils import (
24
+ check_is_sdv_enterprise_included,
25
+ determine_additional_sdv_enterprise_deps,
26
+ display_progress_animation,
27
+ get_latest_package_version,
28
+ get_package_name,
29
+ list_current_installed_packages,
30
+ list_current_installed_packages_with_their_version,
31
+ mask_license_key,
32
+ print_additional_dependencies_installed,
33
+ print_failed_to_connect,
34
+ print_invalid_credentials,
35
+ print_message,
36
+ print_package_summary,
37
+ print_warning_base_connector_package_installed,
38
+ read_stored_packages,
39
+ remove_package_name,
40
+ split_base_and_bundles,
41
+ store_package_name,
42
+ )
43
+
44
+ LOGGER = logging.getLogger(__name__)
45
+
46
+
47
+ def _perform_pip_action(
48
+ action,
49
+ packages,
50
+ version=None,
51
+ index_url=None,
52
+ extra_options=None,
53
+ show_version=False,
54
+ debug=False,
55
+ upgrade=False,
56
+ package_status=None,
57
+ ):
58
+ """Performs pip actions (e.g., install, uninstall) on a list of packages.
59
+
60
+ This function builds and executes pip commands using subprocess for each package
61
+ provided. It supports custom package indexes and additional pip options.
62
+
63
+ Args:
64
+ action (str):
65
+ The pip action to perform (e.g., 'install', 'uninstall').
66
+ packages (list[str]):
67
+ A list of package names to process.
68
+ index_url (str, optional):
69
+ Custom PyPI index URL. Defaults to None.
70
+ extra_options (list[str], optional):
71
+ Additional pip CLI options. Defaults to None.
72
+ debug (bool, optional):
73
+ If True, prints pip commands instead of suppressing output.
74
+ Defaults to False.
75
+ """
76
+ package_status = package_status or {}
77
+ extra_options = extra_options or []
78
+ installed_packages_with_versions = list_current_installed_packages_with_their_version()
79
+ package_version = None
80
+ was_successful = False
81
+
82
+ for package_name in packages:
83
+ package = package_name
84
+ use_version = bool(version or index_url) and package not in CONNECTORS_PACKAGES
85
+ online_access = '--no-index' not in extra_options
86
+
87
+ if use_version and online_access:
88
+ package_version = version or get_latest_package_version(package_name, index_url)
89
+ if upgrade is False:
90
+ package = f'{package_name}=={package_version}' if package_version else package_name
91
+
92
+ elif package in CONNECTORS_PACKAGES and online_access:
93
+ package_version = get_latest_package_version(package_name, index_url)
94
+
95
+ command = f'{sys.executable} -m pip {action} {package or package_name}'
96
+ printable_command = f'\npip {action} {package or package_name}'
97
+ if index_url:
98
+ command = f'{command} --index-url {index_url}'
99
+ printable_command = f'{printable_command} --index-url {mask_license_key(index_url)}'
100
+ trusted_hosts_str = ' '.join(f'--trusted-host {host}' for host in TRUSTED_HOSTS)
101
+ command = f'{command} {trusted_hosts_str}'
102
+ printable_command = f'{printable_command} {trusted_hosts_str}'
103
+
104
+ if upgrade:
105
+ command = f'{command} --upgrade'
106
+ printable_command = f'{printable_command} --upgrade'
107
+
108
+ if extra_options:
109
+ command = f'{command} {" ".join(extra_options)}'
110
+ printable_command = f'{printable_command} {" ".join(extra_options)}'
111
+
112
+ LOGGER.debug('Launching subprocess with: %s', printable_command)
113
+ if debug:
114
+ print_message(printable_command)
115
+ process = subprocess.run(command.split())
116
+
117
+ else:
118
+ process = subprocess.Popen(
119
+ command.split(),
120
+ stdout=subprocess.DEVNULL,
121
+ stderr=subprocess.DEVNULL,
122
+ )
123
+ display_progress_animation(
124
+ process=process,
125
+ package=package_name,
126
+ version=package_version,
127
+ action=action,
128
+ upgrade=upgrade,
129
+ installed_packages=installed_packages_with_versions,
130
+ show_version=show_version,
131
+ )
132
+
133
+ was_successful = process.returncode == ExitCode.SUCCESS
134
+ package_status[package_name] = was_successful
135
+
136
+ if action == 'install' and not extra_options and was_successful:
137
+ store_package_name(package_name)
138
+
139
+ if 'uninstall' in action:
140
+ remove_package_name(package_name)
141
+
142
+ return package_status
143
+
144
+
145
+ def _print_and_perform_action(
146
+ action: str,
147
+ packages: Dict[str, str],
148
+ index_url: Union[str, None],
149
+ debug: bool,
150
+ version: Union[str, None],
151
+ pip_options: Optional[List[str]] = None,
152
+ upgrade: bool = False,
153
+ use_product_type: bool = True,
154
+ ):
155
+ """Print and perform pip action (install/download) for base and bundle packages.
156
+
157
+ Args:
158
+ action (str):
159
+ The pip action to perform (eg. 'install' or 'download').
160
+ packages (dict):
161
+ A dictionary mapping package name to the product type.
162
+ index_url (str):
163
+ The index url to use for pip.
164
+ debug (bool):
165
+ Whether or not to show full print out from pip.
166
+ version (str):
167
+ The version of the package to do the action on.
168
+ pip_options (list):
169
+ List of pip options (eg. --python-version).
170
+ upgrade (bool):
171
+ Whether or not to use upgrade option with pip.
172
+ use_product_type (bool):
173
+ Whether or not to use the product type to aid in messages that get printed.
174
+ """
175
+ base_packages, bundles = split_base_and_bundles(packages, use_product_type)
176
+ action_cap = action.capitalize()
177
+ package_status = {}
178
+
179
+ if base_packages:
180
+ print_message(f'\n{action_cap}ing SDV Enterprise:')
181
+ base_package_statuses = _perform_pip_action(
182
+ action=action,
183
+ packages=base_packages,
184
+ index_url=index_url,
185
+ debug=debug,
186
+ version=version,
187
+ extra_options=pip_options,
188
+ show_version=True,
189
+ upgrade=upgrade,
190
+ package_status=package_status,
191
+ )
192
+ package_status.update(base_package_statuses)
193
+
194
+ if bundles:
195
+ print_message(f'\n{action_cap}ing Bundles:')
196
+ bundle_package_statuses = _perform_pip_action(
197
+ action=action,
198
+ packages=bundles,
199
+ index_url=index_url,
200
+ debug=debug,
201
+ version=version,
202
+ extra_options=pip_options,
203
+ show_version=False,
204
+ upgrade=upgrade,
205
+ )
206
+ package_status.update(bundle_package_statuses)
207
+
208
+ if set(CONNECTORS_PACKAGES).intersection(packages):
209
+ print_warning_base_connector_package_installed()
210
+
211
+ # Determine if sdv-enterprise is actually present
212
+ if action == 'install':
213
+ successful_packages = list_current_installed_packages()
214
+ else:
215
+ successful_packages = [pkg for pkg, status in package_status.items() if status]
216
+
217
+ sdv_included = check_is_sdv_enterprise_included(successful_packages)
218
+ print_package_summary(action, package_status, sdv_included)
219
+
220
+
221
+ def _uninstall_packages_action(packages, debug=False):
222
+ """Uninstall the packages using subprocess / Popen with ``pip``."""
223
+ print_message('Uninstalling:')
224
+ _perform_pip_action('uninstall -y', packages, index_url=None, debug=debug)
225
+
226
+
227
+ def _install_packages_action(
228
+ username, license_key, user_requested_packages, version=None, debug=False, upgrade=False
229
+ ):
230
+ """Install the packages using subprocess / Popen with ``pip``."""
231
+ username_quoted = quote_plus(username, encoding='utf-8')
232
+ index_url = f'https://{username_quoted}:{license_key}@{config.PYPI_URL}/'
233
+
234
+ _print_and_perform_action(
235
+ action='install',
236
+ packages=user_requested_packages,
237
+ index_url=index_url,
238
+ debug=debug,
239
+ version=version,
240
+ pip_options=None,
241
+ upgrade=upgrade,
242
+ )
243
+
244
+
245
+ def _download_packages_action(
246
+ username, license_key, packages, python_version, platform, folder, version=None, debug=False
247
+ ):
248
+ """Download the packages using subprocess / Popen with ``pip download``."""
249
+ username = quote_plus(username, encoding='utf-8')
250
+ index_url = f'https://{username}:{license_key}@{config.PYPI_URL}/'
251
+ pip_options = [
252
+ '--dest',
253
+ folder,
254
+ ]
255
+ if platform:
256
+ pip_options.append('--only-binary=:all:')
257
+ for item in platform:
258
+ pip_options.extend(['--platform', item])
259
+
260
+ if python_version:
261
+ if '--only-binary=:all:' not in pip_options:
262
+ pip_options.append('--only-binary=:all:')
263
+
264
+ pip_options.extend(['--python-version', python_version])
265
+
266
+ _print_and_perform_action(
267
+ action='download',
268
+ packages=packages,
269
+ index_url=index_url,
270
+ debug=debug,
271
+ version=version,
272
+ pip_options=pip_options,
273
+ )
274
+
275
+
276
+ def _get_accessible_packages(username, license_key):
277
+ """Get a dictionary mapping all packages the user can access to their product type."""
278
+ post_data = {'username': username, 'license_key': license_key}
279
+
280
+ try:
281
+ response = requests.post(
282
+ config.API_PACKAGE_PERMISSIONS,
283
+ data=json.dumps(post_data),
284
+ headers=config.HEADERS,
285
+ timeout=config.TIMEOUT,
286
+ )
287
+ except RequestException:
288
+ print_failed_to_connect()
289
+ return None
290
+
291
+ if response.status_code != requests.codes.ok:
292
+ print_invalid_credentials()
293
+ return None
294
+
295
+ parsed_response = response.json()
296
+ LOGGER.debug('Active packages user %s has access to: %s', username, parsed_response)
297
+ accessible_packages = parsed_response.get('packages', [])
298
+ package_to_product_type = {
299
+ pkg['package_name']: pkg['product_type'] for pkg in accessible_packages
300
+ }
301
+ return package_to_product_type
302
+
303
+
304
+ def _get_packages(username, license_key, package=None, options=None):
305
+ """Retrieve a list of available packages to install."""
306
+ package_to_product_type = _get_accessible_packages(username, license_key)
307
+ if not package_to_product_type:
308
+ return
309
+
310
+ if package in package_to_product_type and options:
311
+ options = options if isinstance(options, list) else [options]
312
+ bundle_available_packages = {
313
+ f'{package}[{option}]': ProductType.BUNDLE.value for option in options
314
+ }
315
+ return bundle_available_packages
316
+
317
+ elif package and package not in package_to_product_type:
318
+ print_message(f'Invalid package {package} or no access to the given package.')
319
+ return
320
+
321
+ if package and package in package_to_product_type:
322
+ return {package: package_to_product_type[package]}
323
+
324
+ return package_to_product_type
325
+
326
+
327
+ @authenticate
328
+ def install_packages(
329
+ username=None,
330
+ license_key=None,
331
+ package=None,
332
+ options=None,
333
+ debug=False,
334
+ version=None,
335
+ upgrade=False,
336
+ **kwargs,
337
+ ):
338
+ """Install packages in the current environment (online)."""
339
+ all_sdv_enterprise_related_pkgs = _get_accessible_packages(username, license_key)
340
+ packages_before_install = list_current_installed_packages()
341
+
342
+ user_requested_packages = _get_packages(
343
+ username=username,
344
+ license_key=license_key,
345
+ package=package,
346
+ options=options,
347
+ )
348
+
349
+ _install_packages_action(
350
+ username,
351
+ license_key,
352
+ user_requested_packages=user_requested_packages,
353
+ version=version,
354
+ debug=debug,
355
+ upgrade=upgrade,
356
+ )
357
+ packages_after_install = list_current_installed_packages()
358
+ additional_dependencies_installed = determine_additional_sdv_enterprise_deps(
359
+ packages_before_install=packages_before_install,
360
+ packages_after_install=packages_after_install,
361
+ user_requested_packages=user_requested_packages,
362
+ all_sdv_enterprise_related_pkgs=all_sdv_enterprise_related_pkgs,
363
+ )
364
+ print_additional_dependencies_installed(additional_dependencies_installed)
365
+
366
+
367
+ def install_packages_from_folder(folder, package=None, options=None, debug=False):
368
+ """Install packages from a folder (offline)."""
369
+ wheels = glob.glob(f'{folder}/*.whl')
370
+ packages = set()
371
+ for wheel in wheels:
372
+ package_name = get_package_name(wheel)
373
+ if package_name in EXPECTED_PACKAGES:
374
+ packages.add(package_name)
375
+
376
+ if package:
377
+ if package not in packages:
378
+ print_message(
379
+ f"\nWarning: We couldn’t find the requested package: '{package}' in the provided "
380
+ 'folder, no packages were installed.'
381
+ )
382
+ return
383
+ else:
384
+ packages = [package]
385
+ if options:
386
+ packages = [f'{package}[{option}]' for option in options]
387
+
388
+ pip_options = ['--no-index', f'--find-links {folder}']
389
+ packages_before_install = list_current_installed_packages()
390
+ packages = dict.fromkeys(packages)
391
+
392
+ _print_and_perform_action(
393
+ 'install',
394
+ packages=packages,
395
+ pip_options=pip_options,
396
+ version=None,
397
+ index_url=None,
398
+ debug=debug,
399
+ use_product_type=False,
400
+ )
401
+ packages_after_install = list_current_installed_packages()
402
+
403
+ additional_dependencies_installed = determine_additional_sdv_enterprise_deps(
404
+ packages_before_install=packages_before_install,
405
+ packages_after_install=packages_after_install,
406
+ user_requested_packages=package,
407
+ all_sdv_enterprise_related_pkgs=EXPECTED_PACKAGES,
408
+ )
409
+ print_additional_dependencies_installed(additional_dependencies_installed)
410
+
411
+
412
+ @authenticate
413
+ def _auth_get_packages(username=None, license_key=None, package=None, **kwargs):
414
+ """Wrapper around `_get_packages` to authenticate before using it."""
415
+ return _get_packages(username=username, license_key=license_key, package=package, **kwargs)
416
+
417
+
418
+ def uninstall_packages(
419
+ all_packages=False,
420
+ debug=False,
421
+ **kwargs,
422
+ ):
423
+ """Uninstall previously installed packages or all accessible packages."""
424
+ packages = []
425
+ try:
426
+ packages = read_stored_packages()
427
+
428
+ except (FileNotFoundError, PermissionError, OSError, ValueError):
429
+ print_message('Fetching package list from license server...')
430
+ packages = _auth_get_packages()
431
+
432
+ if not packages:
433
+ print_message('No packages found to uninstall.')
434
+ return
435
+
436
+ current_packages = list_current_installed_packages()
437
+ if set(packages).intersection(set(current_packages)):
438
+ _uninstall_packages_action(packages, debug=debug)
439
+ else:
440
+ print_message('No packages found to uninstall.')
441
+
442
+
443
+ @authenticate
444
+ def download_packages(
445
+ username=None,
446
+ license_key=None,
447
+ package=None,
448
+ python_version=None,
449
+ platform=None,
450
+ folder=None,
451
+ version=None,
452
+ options=None,
453
+ debug=False,
454
+ **kwargs,
455
+ ):
456
+ """Download packages into a local folder for offline installation."""
457
+ packages = _get_packages(
458
+ username=username, license_key=license_key, package=package, options=options
459
+ )
460
+
461
+ if not packages:
462
+ print_message('No packages available for download.')
463
+ return
464
+
465
+ _download_packages_action(
466
+ username=username,
467
+ license_key=license_key,
468
+ packages=packages,
469
+ python_version=python_version,
470
+ platform=platform,
471
+ folder=folder,
472
+ version=version,
473
+ debug=debug,
474
+ )
475
+
476
+
477
+ @authenticate
478
+ def list_packages(username: str, license_key: str):
479
+ """List all packages the user has access to."""
480
+ package_to_product_type = _get_accessible_packages(username, license_key)
481
+
482
+ # Filter bundles
483
+ base_packages, bundles = split_base_and_bundles(package_to_product_type)
484
+ bases_message = '\n'.join(base_packages)
485
+ print_message(f'\nSDV Enterprise:\n{bases_message}')
486
+ if bundles:
487
+ bundles_message = '\n'.join(bundles)
488
+ print_message(f'\nSDV Bundles:\n{bundles_message}')
489
+
490
+ return package_to_product_type
@@ -0,0 +1,52 @@
1
+ """Utility module."""
2
+
3
+ from sdv_installer.utils.data_storage import (
4
+ read_stored_packages,
5
+ remove_package_name,
6
+ store_package_name,
7
+ )
8
+
9
+ from sdv_installer.utils.console_utils import (
10
+ display_progress_animation,
11
+ get_password_input,
12
+ mask_license_key,
13
+ print_failed_to_connect,
14
+ print_invalid_credentials,
15
+ print_message,
16
+ print_package_summary,
17
+ print_warning_base_connector_package_installed,
18
+ print_additional_dependencies_installed,
19
+ )
20
+ from sdv_installer.utils.package_utils import (
21
+ check_is_sdv_enterprise_included,
22
+ get_latest_package_version,
23
+ get_package_name,
24
+ list_current_installed_packages,
25
+ list_current_installed_packages_with_their_version,
26
+ split_base_and_bundles,
27
+ determine_additional_sdv_enterprise_deps,
28
+ )
29
+ from sdv_installer.utils.request_error_handling import handle_http_error_response
30
+
31
+ __all__ = (
32
+ 'check_is_sdv_enterprise_included',
33
+ 'display_progress_animation',
34
+ 'determine_additional_sdv_enterprise_deps',
35
+ 'get_latest_package_version',
36
+ 'get_package_name',
37
+ 'get_password_input',
38
+ 'handle_http_error_response',
39
+ 'list_current_installed_packages',
40
+ 'list_current_installed_packages_with_their_version',
41
+ 'mask_license_key',
42
+ 'print_additional_dependencies_installed',
43
+ 'print_failed_to_connect',
44
+ 'print_invalid_credentials',
45
+ 'print_message',
46
+ 'print_package_summary',
47
+ 'print_warning_base_connector_package_installed',
48
+ 'read_stored_packages',
49
+ 'remove_package_name',
50
+ 'split_base_and_bundles',
51
+ 'store_package_name',
52
+ )