2626//!
2727//! By default it is the **engine only**: no builtin plugins are compiled in.
2828//! The bundled extension set lives in [`cpex-builtins`](cpex_builtins) and is
29- //! pulled in only when a builtins feature is enabled.
29+ //! pulled in only when a builtins feature is enabled. The out-of-process
30+ //! Python plugin host is separate again, behind `python-host`.
3031//!
3132//! # Usage
3233//!
6162//! `audit`, `cedar`, `cel`, `valkey`). When any builtins feature is on, the
6263//! registration helpers and the concrete factory types are re-exported here
6364//! from [`cpex-builtins`](cpex_builtins).
65+ //!
66+ //! `python-host` is orthogonal to all of those, and is in neither `builtins`
67+ //! nor `full`. It pulls
68+ //! [`cpex-hosts-python`](cpex_hosts_python), which runs existing Python CPEX
69+ //! plugins out-of-process — one cached virtualenv and one `worker.py`
70+ //! subprocess per plugin — and re-exports [`IsolatedVenvFactory`] and
71+ //! [`ISOLATED_VENV_KIND`] here. Unlike the builtins there is no
72+ //! `install_*` helper: register the factory yourself, because the `kind` is
73+ //! one host serving arbitrarily many Python plugins.
74+ //!
75+ //! ```ignore
76+ //! use cpex::{IsolatedVenvFactory, PluginManager, ISOLATED_VENV_KIND};
77+ //! use cpex::cpex_core::factory::PluginFactoryRegistry;
78+ //!
79+ //! let mut factories = PluginFactoryRegistry::new();
80+ //! factories.register(ISOLATED_VENV_KIND, Box::new(IsolatedVenvFactory));
81+ //! let mgr = PluginManager::from_config(config, &factories)?;
82+ //! // `initialize()` builds each venv and launches its worker — a cold pip
83+ //! // install is measured in minutes, so do it at startup, not on demand.
84+ //! mgr.initialize().await?;
85+ //! ```
6486
6587// Whole-crate re-exports for advanced use (types not surfaced below).
6688pub use { apl_cmf, apl_core, apl_cpex, cpex_core} ;
@@ -78,6 +100,9 @@ pub use cpex_wasm_host;
78100// The whole aggregator, for advanced use.
79101#[ cfg( feature = "cpex-builtins" ) ]
80102pub use cpex_builtins;
103+ // The whole Python host crate, for advanced use (venv and worker internals).
104+ #[ cfg( feature = "python-host" ) ]
105+ pub use cpex_hosts_python;
81106
82107// Registration helpers — delegated to cpex-builtins, keeping the facade's
83108// historical names (`register_builtin_plugins`, `builtin_pdp_factories`).
@@ -103,6 +128,11 @@ pub use cpex_builtins::{OAuthDelegatorFactory, OAUTH_KIND};
103128pub use cpex_builtins:: { PiiScannerFactory , PII_KIND } ;
104129#[ cfg( feature = "valkey" ) ]
105130pub use cpex_builtins:: { ValkeyConfig , ValkeySessionStoreFactory , VALKEY_KIND } ;
131+ // The Python host's `KIND` is renamed on re-export: bare `KIND` at the facade
132+ // root says nothing about which plugin kind it is, and the builtins above all
133+ // use a prefixed const.
134+ #[ cfg( feature = "python-host" ) ]
135+ pub use cpex_hosts_python:: { IsolatedVenvFactory , KIND as ISOLATED_VENV_KIND } ;
106136
107137#[ cfg( all( test, feature = "cpex-builtins" ) ) ]
108138mod tests {
@@ -115,3 +145,42 @@ mod tests {
115145 install_builtins ( & mgr) ;
116146 }
117147}
148+ #[ cfg( all( test, feature = "python-host" ) ) ]
149+ mod python_host_tests {
150+ use super :: * ;
151+ use cpex_core:: config:: parse_config;
152+ use cpex_core:: factory:: PluginFactoryRegistry ;
153+
154+ /// A config with one `isolated_venv` plugin, mirroring the YAML shape an
155+ /// operator writes. No `plugin_dirs`: the host always resolves
156+ /// `<project root>/plugins` — see `plugin::DEFAULT_PLUGIN_DIR`.
157+ fn minimal_config_yaml ( ) -> & ' static str {
158+ r#"
159+ plugins:
160+ - name: pii-filter
161+ kind: isolated_venv
162+ hooks: [tool_pre_invoke]
163+ config:
164+ class_name: my_pkg.filters.PiiFilter
165+ "#
166+ }
167+
168+ /// The facade's re-exported `IsolatedVenvFactory` and kind const are
169+ /// wired up well enough to instantiate a plugin from config. This stops
170+ /// at `from_config` deliberately — `initialize()` is what builds the venv
171+ /// and spawns `worker.py`, which needs a real interpreter and a real
172+ /// package, so it belongs in cpex-hosts-python's integration tests.
173+ #[ test]
174+ fn from_config_instantiates_the_python_host ( ) {
175+ let config = parse_config ( minimal_config_yaml ( ) ) . expect ( "valid YAML" ) ;
176+
177+ let mut factories = PluginFactoryRegistry :: new ( ) ;
178+ factories. register ( ISOLATED_VENV_KIND , Box :: new ( IsolatedVenvFactory ) ) ;
179+
180+ let mgr = PluginManager :: from_config ( config, & factories)
181+ . expect ( "isolated_venv factory is registered, so instantiation succeeds" ) ;
182+
183+ assert_eq ! ( mgr. plugin_count( ) , 1 ) ;
184+ assert ! ( mgr. has_hooks_for( "tool_pre_invoke" ) ) ;
185+ }
186+ }
0 commit comments