Migrate to a full native extension wrapping protovalidate-cc - #507
Conversation
cc0255a to
2f85e4c
Compare
ded2200 to
926a8e1
Compare
59f4ee9 to
e163e41
Compare
…alidate-python into protovalidate-cc
|
@ajeetdsouza This now uses git submodules instead of local copies for third party deps, and also merges into two crates, deps-sys and protovalidate-sys which includes the actual rust shim. The extraction script still exists to wire up the needed cc files to the build, and also we do still have some local vendoring for gencode that isn't present in the upstream repos (just cel-cpp, protovalidate proto gencode and generated parser). sdist went from 5MB to 18MB, still quite manageable and I see no issue with it (PyPI's limit is 100MB). |
ajeetdsouza
left a comment
There was a problem hiding this comment.
Thanks for collapsing the crates + adding submodules, this looks a lot better now.
| { cmd = "ruff format" } | ||
| ] | ||
|
|
||
| [tasks.format-rust] |
There was a problem hiding this comment.
Might be good to include format-cpp here too via clang-format. There's not much C++ code though, so if it's too much of a pain to install, we can leave it out.
There was a problem hiding this comment.
Yeah since it's small will leave it out for now but look at it later
| sequence = [ | ||
| # Symbols kept: maturin locates pyo3's introspection data through the | ||
| # symbol table, which the release profile's `strip` would remove. | ||
| { cmd = "maturin generate-stubs --profile release --out protovalidate", env = { CARGO_PROFILE_RELEASE_STRIP = "none" } }, |
There was a problem hiding this comment.
I'm guessing we use the release profile here to avoid a separate Rust toolchain + build path in CI, correct?
There was a problem hiding this comment.
Yeah, updated the comment
|
|
||
| """The cel-expr-python (cel-cpp) validation engine.""" | ||
| [package] | ||
| name = "protovalidate-rs" |
There was a problem hiding this comment.
protovalidate would be a better name, and is consistent with our Python / JavaScript libraries.
Performance matters here and popular validation libraries are largely written in native. We took one step there by replacing our CEL engine with cel-expr-python, but that left several problems. The idea of utilizing protovalidate-cc as a native core came up, but a Python project must not require Bazel to allow smooth sdist builds and to improve contributor experience. At first this seemed like a blocker, but this takes an approach that I think is viable.
Instead of running Bazel on every build, this uses it as a preprocessing step to vendor in C++ sources for us to wrap into a standard PyO3 + cargo build. This relies on certain observations
-syscrates heavily to expose existing native libraries and includes robust support for building them with it'scccrate. This isn't random spawning of gccSo this adds a script, extract_native_sources, which fetches the protovalidate-cc repo and uses Bazel to build it, which fetches in its dependency sources, and we use
bazel aqueryto analyse the build graph for the exact source files used. We vendor the sources and a manifest of the files list to feed to standard cargoccmachinery, to have a simple cargo build for the libraries that sidesteps Bazel completely.This isn't a simple approach by any means but it is systematic and I believe robust - we don't have any shady regex matching, we use bazel's queries to get real info on the build. IMO it's the best way to follow our standing directive of reusing cel-cpp here. And the approach could be used to implement a protovalidate-rust in the future that still uses the cpp CEL engine - if that happened, protovalidate-python and protovalidate-cpp would both wrap that.
Result, every problem we have is eliminated
Improvement, perhaps still a problem though much less of a deal - we only copy once between protobuf-py and protobuf-cpp, no upb copy in between like before.
First bench is from an interim state.
Notice how celpy is just way too slow, so even keeping it as a fallback was somewhat questionable in whether it could actually be used. There are still quite some good wins going from the cel-expr approach to this.
This is the final one after some more optimizations especially when returning errors - didn't even record celpy since doesn't matter much.
The gap to pydantic is much reduced. We will still need to improve further - this will involve implementing native rules in protovalidate-cc as we have done in some of the other languages. Profiling shows most time is spent in CC, not our bindings (which includes the marshaling cost of the protos to C++).
Just for context, I did some highly experimental native rule evaluation in the vendored protovalidate-cc for a good improvement.
Fixes #489