Skip to content

Commit 30157ba

Browse files
committed
chore: Fixed the Python bindings build after the dev branch merge: updated Cargo.toml and builtins.rs to use the new cpex-builtins facade, then resolved two clippy lint failures.
Signed-off-by: habeck <habeck@us.ibm.com>
1 parent 94bb97c commit 30157ba

8 files changed

Lines changed: 79 additions & 102 deletions

File tree

Cargo.lock

Lines changed: 24 additions & 13 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

bindings/python/Cargo.toml

Lines changed: 12 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -28,19 +28,21 @@ name = "_lib"
2828
crate-type = ["cdylib"]
2929

3030
[dependencies]
31-
cpex-core = { path = "../../crates/cpex-core" }
31+
cpex-core = { workspace = true }
3232

3333
# APL governance layer — same set as cpex-ffi so bundled factories are
3434
# available to Python callers. Keep in sync with crates/cpex-ffi/Cargo.toml.
35-
apl-cpex = { path = "../../crates/apl-cpex" }
36-
apl-pii-scanner = { path = "../../crates/apl-pii-scanner" }
37-
apl-audit-logger = { path = "../../crates/apl-audit-logger" }
38-
apl-identity-jwt = { path = "../../crates/apl-identity-jwt" }
39-
apl-delegator-oauth = { path = "../../crates/apl-delegator-oauth" }
40-
apl-pdp-cedar-direct = { path = "../../crates/apl-pdp-cedar-direct" }
41-
42-
# Heavy Cedarling deps behind an opt-in feature — off by default.
43-
apl-cedarling = { path = "../../crates/apl-cedarling", optional = true }
35+
apl-cpex = { workspace = true }
36+
# The builtin extension set — mirrors the feature set shipped by cpex-ffi.
37+
cpex-builtins = { workspace = true, default-features = false, features = [
38+
"pii-scanner",
39+
"audit-logger",
40+
"identity-jwt",
41+
"delegator-oauth",
42+
"cedar-direct",
43+
"cel",
44+
"valkey",
45+
] }
4446

4547
serde = { workspace = true }
4648
serde_json = { workspace = true }
@@ -56,4 +58,3 @@ pyo3-async-runtimes = { version = "0.29", features = ["tokio-runtime"] }
5658

5759
[features]
5860
default = []
59-
cedarling = ["dep:apl-cedarling"]

bindings/python/src/builtins.rs

Lines changed: 4 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -8,50 +8,14 @@
88
// passed to `load_config_yaml` so the APL visitor's `Weak<PluginManager>`
99
// upgrades correctly during load.
1010
//
11-
// Mirrors `crates/cpex-ffi/src/apl.rs:56` exactly — any new bundled factory
12-
// added to cpex-ffi should be added here too.
13-
//
14-
// Ordering (per apl.rs header comment):
15-
// PluginManager::default()
16-
// → register_builtin_factories (this function)
17-
// → load_config_yaml
18-
// → initialize
11+
// Delegates to `cpex_builtins::install_builtins`, which mirrors the factory
12+
// set shipped by cpex-ffi. Any new builtin added to cpex-builtins is
13+
// automatically included here via the shared feature flags.
1914

2015
use std::sync::Arc;
2116

2217
use cpex_core::manager::PluginManager;
2318

2419
pub fn register_builtin_factories(manager: &Arc<PluginManager>) {
25-
// Plugin factories — registered by `kind` string. Must happen before
26-
// load_config_yaml so the manager can instantiate plugins whose YAML
27-
// `kind:` matches.
28-
manager.register_factory(
29-
apl_pii_scanner::KIND,
30-
Box::new(apl_pii_scanner::PiiScannerFactory),
31-
);
32-
manager.register_factory(
33-
apl_audit_logger::KIND,
34-
Box::new(apl_audit_logger::AuditLoggerFactory),
35-
);
36-
manager.register_factory(
37-
apl_identity_jwt::KIND,
38-
Box::new(apl_identity_jwt::JwtIdentityFactory),
39-
);
40-
manager.register_factory(
41-
apl_delegator_oauth::KIND,
42-
Box::new(apl_delegator_oauth::OAuthDelegatorFactory),
43-
);
44-
45-
// APL config visitor + PDP factories.
46-
let mut opts = apl_cpex::AplOptions::in_process();
47-
opts.pdp_factories = vec![Arc::new(apl_pdp_cedar_direct::CedarDirectPdpFactory::new())];
48-
apl_cpex::register_apl(manager, opts);
49-
50-
// Cedarling-backed identity + PDP seams (opt-in; heavy deps).
51-
#[cfg(feature = "cedarling")]
52-
{
53-
// Wire Cedarling factories when the feature is enabled.
54-
// Keep in sync with cpex-ffi's cedarling feature block in apl.rs.
55-
let _ = manager; // suppress unused-variable warning if no-op
56-
}
20+
cpex_builtins::install_builtins(manager);
5721
}

bindings/python/src/conversions.rs

Lines changed: 13 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,11 @@ cpex_core::impl_plugin_payload!(GenericPayload);
4343
/// (with `str` keys). Any other type raises `ValueError` naming the type.
4444
///
4545
/// Recursion is capped at 128 levels (R3). `depth` starts at 0.
46-
pub fn pyobj_to_json_value(py: Python<'_>, obj: &Bound<'_, PyAny>, depth: usize) -> PyResult<Value> {
46+
pub fn pyobj_to_json_value(
47+
_py: Python<'_>,
48+
obj: &Bound<'_, PyAny>,
49+
depth: usize,
50+
) -> PyResult<Value> {
4751
if depth > 128 {
4852
return Err(PyValueError::new_err(
4953
"cpex: value nesting exceeds maximum depth of 128 levels",
@@ -75,19 +79,17 @@ pub fn pyobj_to_json_value(py: Python<'_>, obj: &Bound<'_, PyAny>, depth: usize)
7579
if let Ok(lst) = obj.cast::<PyList>() {
7680
let mut out = Vec::with_capacity(lst.len());
7781
for item in lst.iter() {
78-
out.push(pyobj_to_json_value(py, &item, depth + 1)?);
82+
out.push(pyobj_to_json_value(_py, &item, depth + 1)?);
7983
}
8084
return Ok(Value::Array(out));
8185
}
8286
if let Ok(d) = obj.cast::<PyDict>() {
8387
let mut map = Map::with_capacity(d.len());
8488
for (k, v) in d.iter() {
8589
let key: String = k.extract().map_err(|_| {
86-
PyValueError::new_err(
87-
"cpex: dict keys must be strings; got a non-string key",
88-
)
90+
PyValueError::new_err("cpex: dict keys must be strings; got a non-string key")
8991
})?;
90-
map.insert(key, pyobj_to_json_value(py, &v, depth + 1)?);
92+
map.insert(key, pyobj_to_json_value(_py, &v, depth + 1)?);
9193
}
9294
return Ok(Value::Object(map));
9395
}
@@ -124,22 +126,22 @@ pub fn json_value_to_pyobj<'py>(py: Python<'py>, v: &Value) -> PyResult<Bound<'p
124126
"cpex: JSON number {n} is out of range for Python"
125127
)))
126128
}
127-
}
129+
},
128130
Value::String(s) => Ok(s.into_pyobject(py)?.into_any()),
129131
Value::Array(arr) => {
130132
let lst = PyList::empty(py);
131133
for item in arr {
132134
lst.append(json_value_to_pyobj(py, item)?)?;
133135
}
134136
Ok(lst.into_any())
135-
}
137+
},
136138
Value::Object(map) => {
137139
let d = PyDict::new(py);
138140
for (k, val) in map {
139141
d.set_item(k, json_value_to_pyobj(py, val)?)?;
140142
}
141143
Ok(d.into_any())
142-
}
144+
},
143145
}
144146
}
145147

@@ -198,9 +200,8 @@ pub fn serialize_payload(payload: &dyn PluginPayload) -> Option<Value> {
198200
///
199201
/// An empty dict yields `Extensions::default()` (all fields `#[serde(default)]`).
200202
pub fn extensions_from_value(value: Value) -> PyResult<Extensions> {
201-
serde_json::from_value(value).map_err(|e| {
202-
PyValueError::new_err(format!("cpex: extensions conversion failed: {e}"))
203-
})
203+
serde_json::from_value(value)
204+
.map_err(|e| PyValueError::new_err(format!("cpex: extensions conversion failed: {e}")))
204205
}
205206

206207
/// Deserialize Python dict → `Option<PluginContextTable>` via serde.

bindings/python/src/error.rs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,14 +20,15 @@ use pyo3::PyErr;
2020
/// path — denials surface as `PipelineResult { continue_processing: false,
2121
/// violation: Some(...) }`, never as an `Err`. Kept here as a defensive
2222
/// catch-all that maps to `RuntimeError`.
23+
#[allow(clippy::boxed_local)]
2324
pub fn plugin_error_to_pyerr(e: Box<PluginError>) -> PyErr {
2425
match *e {
2526
PluginError::Config { message } => {
2627
PyValueError::new_err(format!("cpex config error: {message}"))
27-
}
28+
},
2829
PluginError::UnknownHook { hook_type } => {
2930
PyValueError::new_err(format!("cpex unknown hook type: {hook_type}"))
30-
}
31+
},
3132
PluginError::Timeout {
3233
plugin_name,
3334
timeout_ms,

bindings/python/src/lib.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,15 +36,15 @@ fn worker_threads_from_env() -> Option<usize> {
3636
ENV_WORKER_THREADS,
3737
);
3838
Some(n)
39-
}
39+
},
4040
_ => {
4141
tracing::warn!(
4242
"cpex-python: {}={:?} is not a positive integer; using num_cpus default",
4343
ENV_WORKER_THREADS,
4444
raw,
4545
);
4646
None
47-
}
47+
},
4848
}
4949
}
5050

bindings/python/src/manager.rs

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,9 @@ impl PyPluginManager {
6767

6868
let manager = Arc::new(PluginManager::default());
6969
register_builtin_factories(&manager);
70-
manager.load_config_yaml(&yaml).map_err(plugin_error_to_pyerr)?;
70+
manager
71+
.load_config_yaml(&yaml)
72+
.map_err(plugin_error_to_pyerr)?;
7173

7274
Ok(Self { inner: manager })
7375
}
@@ -164,8 +166,9 @@ impl PyPluginManager {
164166
// runtime — the outer timeout ensures we never block indefinitely.
165167
future_into_py(py, async move {
166168
let result = tokio::time::timeout(PY_WALL_CLOCK_TIMEOUT, async move {
167-
let (pipeline_result, _bg_tasks) =
168-
manager.invoke_by_name(&hook_name, rust_payload, rust_extensions, rust_context).await;
169+
let (pipeline_result, _bg_tasks) = manager
170+
.invoke_by_name(&hook_name, rust_payload, rust_extensions, rust_context)
171+
.await;
169172
// _bg_tasks dropped here; fire-and-forget tasks keep running
170173
// on the manager's TaskTracker and are drained by shutdown() (KD4).
171174
pipeline_result_to_py(pipeline_result)

bindings/python/src/result.rs

Lines changed: 15 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,8 @@
1515

1616
use std::collections::HashMap;
1717

18-
use cpex_core::executor::PipelineResult;
1918
use cpex_core::error::PluginErrorRecord;
19+
use cpex_core::executor::PipelineResult;
2020
use pyo3::prelude::*;
2121
use pyo3::types::PyDict;
2222
use serde_json::Value;
@@ -52,7 +52,7 @@ impl PyPipelineResult {
5252
"cpex: modified_payload is not a dict",
5353
)
5454
})?))
55-
}
55+
},
5656
}
5757
}
5858

@@ -67,7 +67,7 @@ impl PyPipelineResult {
6767
"cpex: modified_extensions is not a dict",
6868
)
6969
})?))
70-
}
70+
},
7171
}
7272
}
7373

@@ -78,11 +78,9 @@ impl PyPipelineResult {
7878
Some(v) => {
7979
let obj = json_value_to_pyobj(py, v)?;
8080
Ok(Some(obj.cast_into::<PyDict>().map_err(|_| {
81-
pyo3::exceptions::PyRuntimeError::new_err(
82-
"cpex: violation is not a dict",
83-
)
81+
pyo3::exceptions::PyRuntimeError::new_err("cpex: violation is not a dict")
8482
})?))
85-
}
83+
},
8684
}
8785
}
8886

@@ -93,9 +91,7 @@ impl PyPipelineResult {
9391
.map(|v| {
9492
let obj = json_value_to_pyobj(py, v)?;
9593
obj.cast_into::<PyDict>().map_err(|_| {
96-
pyo3::exceptions::PyRuntimeError::new_err(
97-
"cpex: error entry is not a dict",
98-
)
94+
pyo3::exceptions::PyRuntimeError::new_err("cpex: error entry is not a dict")
9995
})
10096
})
10197
.collect()
@@ -108,11 +104,9 @@ impl PyPipelineResult {
108104
Some(v) => {
109105
let obj = json_value_to_pyobj(py, v)?;
110106
Ok(Some(obj.cast_into::<PyDict>().map_err(|_| {
111-
pyo3::exceptions::PyRuntimeError::new_err(
112-
"cpex: metadata is not a dict",
113-
)
107+
pyo3::exceptions::PyRuntimeError::new_err("cpex: metadata is not a dict")
114108
})?))
115-
}
109+
},
116110
}
117111
}
118112

@@ -128,7 +122,11 @@ impl PyPipelineResult {
128122
format!(
129123
"PipelineResult(continue_processing={}, violation={}, errors={})",
130124
self.continue_processing,
131-
if self.violation.is_some() { "Some(...)" } else { "None" },
125+
if self.violation.is_some() {
126+
"Some(...)"
127+
} else {
128+
"None"
129+
},
132130
self.errors.len(),
133131
)
134132
}
@@ -150,9 +148,7 @@ pub fn pipeline_result_to_py(mut result: PipelineResult) -> PyResult<PyPipelineR
150148
Some(p) => match serialize_payload(p.as_ref()) {
151149
Some(v) => Some(v),
152150
None => {
153-
tracing::warn!(
154-
"cpex-python: modified payload could not be serialised; dropping"
155-
);
151+
tracing::warn!("cpex-python: modified payload could not be serialised; dropping");
156152
result.errors.push(PluginErrorRecord {
157153
plugin_name: "<py>".to_string(),
158154
message: "modified payload could not be serialised across the PyO3 boundary"
@@ -162,7 +158,7 @@ pub fn pipeline_result_to_py(mut result: PipelineResult) -> PyResult<PyPipelineR
162158
proto_error_code: None,
163159
});
164160
None
165-
}
161+
},
166162
},
167163
};
168164

0 commit comments

Comments
 (0)