Module: OpenC3::PythonVenv
- Defined in:
- lib/openc3/utilities/python_venv.rb
Constant Summary collapse
- PLUGIN_VENVS_DIR =
'/gems/plugin_venvs'- DEFAULT_PYTHONUSERBASE =
Shared user site-packages directory used by plugins that predate the per-plugin UV venvs. Every caller that falls back off a plugin venv points PYTHONUSERBASE here, so keep it in one place rather than repeating the literal in script.rb, running_script.rb, microservice_operator.rb and microservice_model.rb.
'/gems/python_packages'- RESERVED_PACKAGES =
Packages a plugin venv must never supply itself. The scripts and microservices that use a plugin venv still run the base venv's Python binary, and CPython searches every PYTHONPATH entry ahead of the running venv's own site-packages. A plugin that declares openc3 as a dependency would therefore shadow the system library with its own copy, silently swapping the API client the script talks to COSMOS with. Reordering PYTHONPATH cannot fix this - the whole variable outranks the base venv - so the copy is removed at install time instead.
['openc3'].freeze
Class Method Summary collapse
- .configure_environment(environment, venv_dir) ⇒ Object
-
.configure_for_script(environment, name:, scope:, python_venv: nil) ⇒ Object
Resolve a script's plugin venv and expose its packages to a child process.
- .plugin_venv_path(scope:, plugin_name:) ⇒ Object
-
.purge_reserved_packages(venv_dir) ⇒ Object
Remove any RESERVED_PACKAGES a plugin installed into its own venv.
Class Method Details
.configure_environment(environment, venv_dir) ⇒ Object
50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 |
# File 'lib/openc3/utilities/python_venv.rb', line 50 def self.configure_environment(environment, venv_dir) environment['VIRTUAL_ENV'] = venv_dir environment['PATH'] = "#{venv_dir}/bin:#{ENV.fetch('PATH', '')}" environment['PYTHONUSERBASE'] = venv_dir site_packages = Dir.glob("#{venv_dir}/lib/python*/site-packages").first # Prefer a PYTHONPATH the caller already seeded on `environment` over the # ambient one. Script.process_suite sets it to ENV['PYTHONPATH'] || '.' # before calling here, and reading ENV directly would drop that value. existing_pythonpath = environment['PYTHONPATH'] || ENV.fetch('PYTHONPATH', '') if site_packages environment['PYTHONPATH'] = existing_pythonpath.empty? ? site_packages : "#{site_packages}:#{existing_pythonpath}" else environment['PYTHONPATH'] = existing_pythonpath.empty? ? nil : existing_pythonpath end environment end |
.configure_for_script(environment, name:, scope:, python_venv: nil) ⇒ Object
Resolve a script's plugin venv and expose its packages to a child process. Returns the venv path when configured, or nil when the system environment should be used.
42 43 44 45 46 47 48 |
# File 'lib/openc3/utilities/python_venv.rb', line 42 def self.configure_for_script(environment, name:, scope:, python_venv: nil) venv_dir = resolve_script_venv(name: name, scope: scope, python_venv: python_venv) return nil unless venv_dir configure_environment(environment, venv_dir) venv_dir end |
.plugin_venv_path(scope:, plugin_name:) ⇒ Object
99 100 101 102 103 |
# File 'lib/openc3/utilities/python_venv.rb', line 99 def self.plugin_venv_path(scope:, plugin_name:) sanitized_name = "#{scope}__#{plugin_name}".tr('^a-zA-Z0-9_-', '_') candidate = File.join(PLUGIN_VENVS_DIR, sanitized_name, '.venv') candidate if File.directory?(candidate) end |
.purge_reserved_packages(venv_dir) ⇒ Object
Remove any RESERVED_PACKAGES a plugin installed into its own venv. Returns the names that were removed so callers can report them.
71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 |
# File 'lib/openc3/utilities/python_venv.rb', line 71 def self.purge_reserved_packages(venv_dir) return [] if venv_dir.nil? || venv_dir.empty? purged = [] RESERVED_PACKAGES.each do |package| next unless package_installed?(venv_dir, package) Logger.warn("Plugin venv #{venv_dir} declares '#{package}', which would shadow the " \ "system library. Removing it; the plugin will use the system #{package}.") _output, status = Open3.capture2e('uv', 'pip', 'uninstall', '--python', venv_dir, package) if status.success? purged << package else Logger.error("Failed to remove '#{package}' from plugin venv #{venv_dir}") end end purged rescue => e Logger.error("Could not purge reserved packages from #{venv_dir}: #{e.}") [] end |