Python-first auto-optimizer that rustifies hotspots (skeleton repo)
Project description
PyRust (skeleton)
PyRust is a Python-first library that aims to automatically identify performance hotspots in a Python project and "rustify" them by generating and compiling Rust extensions (via PyO3 + maturin), with safe fallback to Python.
This repository delivers an end-to-end workflow: profiling, rustyficability analysis, transpilation, hot-swap reloads, and an automatic pipeline that builds Rust extensions through PyO3 + maturin with safe Python fallback. También contempla la reversión del último hot-swap y el control de caché en el pipeline automático.
📚 Documentación: https://pyrust-dev.github.io/pyrust/ (versión HTML generada con MkDocs) · https://github.com/pyrust-dev/pyrust/tree/main/docs (fuentes en GitHub) 🗒️ Notas de publicación: https://github.com/pyrust-dev/pyrust/releases
Índice
- Instalación
- Ejemplo rápido
- Compatibilidad resumida
- Perfilado rápido con
profile_project - Recarga de extensiones y hot-swap seguro
- Analizador de rustyficabilidad
- Transpilación con filtro del analizador
- Ejemplos
- Quick start (dev)
- Comprobación recomendada (entorno aislado)
- Extras opcionales
- Build the Rust extension
- Generar la rueda nativa (release)
- Flujo de publicación con maturin
- Integración continua (Fase 1)
- Layout
Guía rápida para PyPI
- Objetivo del paquete: exponer una librería Python con aceleradores en Rust para perfilar, analizar y probar la "rustificación" de proyectos.
- Plataformas con rueda publicada: Linux x86_64/aarch64, macOS x86_64/arm64 y Windows x86_64.
- Dependencias mínimas: Python 3.10+ para ejecutar la librería y las CLI; Rust 1.70+ solo si quieres compilar desde fuente.
- Entrypoints instalados:
pyrust,pyrust-profile,pyrust-analyzey el módulopyrustpara importación directa.
Instalación directa desde PyPI:
pip install pyrust
Instalación de prueba desde TestPyPI (útil para validar lanzamientos previos):
pip install --index-url https://test.pypi.org/simple/ --extra-index-url https://pypi.org/simple pyrust
Verificación rápida tras la instalación:
python -c "import pyrust; print(pyrust.hello())"
pyrust-profile --help
Para el flujo completo de publicación consulta https://pyrust-dev.github.io/pyrust/publish/ y, si necesitas reproducir la rueda localmente, usa el objetivo make build-native.
Instalación
pip install pyrust
Requisitos mínimos:
- Python 3.10+ para ejecutar la librería y las herramientas de línea de comandos.
- Rust 1.70+ (edición 2021) solo es necesario si quieres compilar desde el código fuente o generar ruedas tú mismo; no se requiere para usar las ruedas ya publicadas.
Ejemplo rápido
Importa el paquete instalado y usa la API básica:
import pyrust
print(pyrust.hello())
print(pyrust.add(2, 3))
Entrypoints de CLI instalados con la rueda estándar (incluye el binario principal pyrust):
# Perfilado rápido de un proyecto o script
pyrust-profile examples --limit 5
# Análisis de rustyficabilidad con salida en tabla
pyrust-analyze my_project --limit 3
# CLI unificada (subcomandos principales)
pyrust rustificar my_project --buscar --format table
pyrust auto my_project --hotspot-limit 5 --min-runtime-pct 10
Compatibilidad resumida
| Python | Rust (solo para build desde fuente) | Ruedas publicadas (SO) | Enlaces de detalle |
|---|---|---|---|
| 3.10+ | 1.70+ | Linux x86_64/aarch64, macOS x86_64/arm64, Windows x86_64 | Generar la rueda nativa, Flujo de publicación |
Perfilado rápido con profile_project
PyRust incluye un perfilador ligero para localizar hotspots antes de rustyficar.
El punto de entrada público es pyrust.profile_project(path, *, entrypoint=None, command=None, limit=None, use_sys_profile=True, include_stdlib=False, include_site_packages=False, output_path=None).
- Si
pathapunta a un archivo, se ejecuta directamente conrunpy.run_path. - Si
pathes un directorio, se busca__main__.pyo elentrypointproporcionado. commandpermite pasar un callable ya preparado (por ejemplo, una función de pruebas).limitrecorta el número de filas del resumen (útil para tablas compactas).use_sys_profileactiva/desactivacProfile(el hook de alto nivel se mantiene para medir tiempo total).include_stdlibañade llamadas de la stdlib a la tabla (por defecto se filtran para centrarse en tu código).include_site_packagesincluye dependencias externas (site-packages/dist-packages) y entornos virtuales vendorizados.output_pathpermite volcar el JSON serializado al disco tras el perfilado.
Ejemplo mínimo desde un REPL o script:
from pathlib import Path
from pyrust import profile_project
summary = profile_project(Path("examples"))
print(f"Tiempo total: {summary.total_runtime:.3f}s")
print(summary.to_table(limit=5))
# Guardar el JSON para análisis posteriores
summary.to_json("profile_report.json")
CLI equivalente (instalación editable o entorno con el entrypoint pyrust-profile disponible):
pyrust-profile examples --limit 5 --include-stdlib --no-use-sys-profile --output-path profile_report.json
Salida típica (hotspots ordenados por total_time):
# | total_time (s) | runtime (%) | call_count | avg_time (s) | function
-----------------------------------------------------------------------
1 | 0.152341 | 90.30 | 2 | 0.076170 | /ruta/proyecto/examples/basic.py:11:run
2 | 0.010250 | 6.08 | 10 | 0.001025 | /ruta/proyecto/examples/utils.py:45:expensive_op
3 | 0.001902 | 1.13 | 1 | 0.001902 | /ruta/proyecto/examples/basic.py:22:main
Cómo leer la tabla de hotspots:
- total_time: tiempo acumulado en la función (mayor → más candidato a rustyficar).
- call_count: número de llamadas (útil para detectar funciones ligeras pero muy frecuentes).
- avg_time: tiempo medio por llamada; si es alto, conviene optimizar la función individualmente.
Usa include_stdlib=True si necesitas ver el impacto de dependencias estándar y ajusta limit
para centrarte en los hotspots principales.
Recarga de extensiones y hot-swap seguro
La fachada pyrust.runtime.reload_extension crea internamente un RuntimeManager, aplica
la política de caché indicada y devuelve un ReloadResult con el módulo cargado y las
métricas de recarga. Puedes adjuntar validadores y hooks de éxito/fallo/rollback; el
gestor usado queda expuesto en ReloadResult.runtime_manager para reusar la misma
instancia en recargas posteriores y consultar last_metrics.
En procesos largos o en pipelines de CI, puedes cerrar explícitamente el runtime con
shutdown_runtime() para liberar la caché y evitar que queden artefactos cargados antes
de iniciar otra ejecución.
Ejemplo básico con validación y sustitución de funciones en vivo:
from pathlib import Path
from pyrust.runtime import reload_extension
runtime = None
def validator(module: object) -> bool:
return hasattr(module, "compute")
def on_success(name, loaded, metrics):
print(f"{name} swap #{metrics.swap_count}; cache={metrics.used_cache}")
result_v1 = reload_extension(
"my_hot_ext",
source=Path("/tmp/my_hot_ext_v1.py"),
cache_dir=Path(".cache/pyrust"),
validate=validator,
on_success=on_success,
)
runtime = result_v1.runtime_manager
# Recarga con otra versión y reusa el mismo gestor para mantener el estado
result_v2 = reload_extension(
"my_hot_ext",
source=Path("/tmp/my_hot_ext_v2.py"),
runtime_manager=runtime,
force_recompile=True,
)
runtime.replace_function(target_object, "compute", result_v2.loaded.module.compute)
print("Último swap:", runtime.last_metrics.swap_count)
Opciones adicionales: cache_manager (para usar uno existente), compiler (callable
que genere el artefacto), event_hooks (tabla inicial de hooks por evento) y banderas
force_recompile/invalidate_cache/use_cache que controlan el origen del artefacto.
La recarga requiere un artefacto explícito: proporciona source o un path directo;
no se realizan importaciones implícitas desde sys.path.
Analizador de rustyficabilidad
El analizador revisa módulos y funciones para estimar su compatibilidad con una conversión a Rust. Recorre el AST con una política conservadora:
- Reglas AST: se permiten expresiones aritméticas y booleanas simples, bucles sobre
range/enumerateo contenedores básicos, y condiciones de comparaciones sencillas.yield/yield fromy comprensiones generanPARTIAL. - Heurísticas de tipos: las anotaciones primitivas (
int,float,bool,str, etc.) son aceptadas. Contenedores sin parametrizar o anotaciones ambiguas (Any,object) degradan aPARTIAL. Tipos complejos/personalizados o variádicos sin anotar se marcan comoNO. - Política conservadora: IO, reflexión, async/await, acceso dinámico a
__dict__o mutaciones complejas generan razones bloqueantes. Si hay dudas, se degrada el veredicto y se registran todas las razones.
API Python
from pathlib import Path
from pyrust import analyze_summary
summary = analyze_summary(Path("src"), excluded_dirs=["venv", "__pycache__"])
print(summary.to_dict()["counts"])
for result in summary.blockers:
print(f"Bloqueante: {result.target} -> {result.reasons[0]}")
CLI
Se instala el entrypoint pyrust-analyze (o puedes usar python -m pyrust.analyzer):
# Tabla legible con los primeros hallazgos
pyrust-analyze my_project --exclude venv --exclude build --limit 3
# JSON estructurado (ideal para pipelines)
pyrust-analyze my_project --format json
# Mostrar todas las razones encontradas por objetivo
pyrust-analyze my_project --show-reasons
La salida incluye conteos por veredicto (FULL, PARTIAL, NO) y listados de objetivos
parciales o bloqueantes con sus razones completas (todas las razones si se usa
--show-reasons).
También puedes usar pyrust rustificar para ejecutar el análisis y la transpilación
en un solo paso:
# Rustificar el proyecto actual
pyrust rustificar
# Rustificar un archivo puntual
pyrust rustificar src/modulo.py
# Generar resumen en Markdown y guardarlo en un archivo
pyrust rustificar my_project --format markdown --output rustyficacion.md
# Deshacer el último hot-swap registrado
pyrust rustificar --deshacer
El subcomando imprime un resumen con targets procesados, funciones omitidas y errores,
y puede incluir el código generado usando --mostrar-codigo.
Transpilación con filtro del analizador
Puedes encadenar el analizador con el transpiler usando la API pública
pyrust.transpile_with_analysis(path, *, backend=None, excluded_dirs=None). La
función ejecuta el análisis primero, salta funciones con veredicto NO y añade
el veredicto como metadato en el IRFunction antes de renderizar con el backend
elegido (RustTemplateBackend por defecto).
from pathlib import Path
from pyrust import transpile_with_analysis
results = transpile_with_analysis(Path("src"))
for item in results:
if item.skipped:
print(f"Saltada por veredicto {item.verdict.name}: {item.target}")
else:
print(f"Renderizada: {item.target}\n{item.rendered}")
Si se intenta renderizar manualmente una función con veredicto NO, el
transpiler devolverá un error claro (IRValidationError) en vez de generar
plantilla incompleta.
Ejemplos
Consulta examples/README.md para ejecutar los ejemplos rápidos:
- Compila el módulo nativo:
maturin develop. - Verifica la integración Rust:
python examples/basic.py. - Fuerza el modo solo-Python:
PYRUST_FORCE_PYTHON_FALLBACK=1 python examples/fallback.py.
Quick start (dev)
python -m venv .venv
source .venv/bin/activate # Windows: .venv\Scripts\activate
pip install -U pip
pip install -e "[dev]"
Comprobación recomendada (entorno aislado)
Para validar que las dependencias de desarrollo se instalan correctamente y que el módulo funciona después de compilarse con maturin, puedes ejecutar:
pytest tests/test_dev_environment_setup.py -k dev_environment_install_and_import
La prueba crea un entorno virtual temporal, instala .[dev], ejecuta maturin develop con CARGO_TARGET_DIR apuntando a un directorio efímero y verifica que pyrust.hello() y pyrust.add() funcionan, sin dejar artefactos en el repositorio.
Compatibilidad: Python 3.10+ y Rust 1.70+ (edición 2021) funcionan correctamente con la versión actual de PyO3 y maturin.
Calidad y seguridad (comandos locales)
Para replicar los checks de CI en local:
ruff check .
ruff format --check .
mypy pyrust
bandit -r pyrust
pip-audit
Extras opcionales
- Dev (tooling de contribución: linting, pruebas y build nativo):
pip install -e "[dev]". - Rust (tooling de compilación):
pip install -e "[rust]". - Ejemplos (IPython + helpers de consola para depuración):
pip install -e "[examples]". - Docs (generar documentación estática con MkDocs):
pip install -e "[docs]".
Build the Rust extension
maturin develop
Generar la rueda nativa (release)
Para producir la rueda compilada usa el objetivo de Make:
make build-native
El objetivo ejecuta maturin build --release -m rust/Cargo.toml y deja el artefacto generado en target/wheels/ (p. ej., target/wheels/pyrust_native-<version>-*.whl).
Verificación opcional del módulo nativo
Después de construir la rueda puedes validarla con:
make verify-native
El paso anterior crea un entorno virtual temporal, instala la rueda recién construida y ejecuta python -c "import pyrust; print(pyrust.hello())" para comprobar que el módulo _native carga correctamente.
Flujo de publicación con maturin
Para un checklist detallado y paso a paso (versionado, changelog, build, validación y publicación), consulta https://pyrust-dev.github.io/pyrust/publish/.
Requisitos previos:
- Python 3.10+ y Rust 1.70+ (edición 2021) instalados.
maturin>=1.6disponible en el entorno (pip install maturin).- Variable opcional
CARGO_TARGET_DIRapuntando a un directorio de artefactos (p. ej.,export CARGO_TARGET_DIR="$(pwd)/target") para mantener los builds fuera de otras carpetas.
Pasos locales para generar y verificar artefactos antes de publicar:
# Compila la rueda en modo release reutilizando el script del repo
CARGO_TARGET_DIR="$(pwd)/target" ./scripts/build_native.sh
# Valida que la rueda generada se puede instalar y cargar
./scripts/verify_native.sh
# Instalación manual (opcional) de la rueda recién creada
python -m pip install target/wheels/*.whl
Publicación manual a PyPI o TestPyPI desde el entorno local (usa tokens almacenados como variables de entorno):
# TestPyPI (rama de pruebas)
MATURIN_PASSWORD="$TEST_PYPI_API_TOKEN" maturin publish \
--username __token__ \
--password "$MATURIN_PASSWORD" \
--repository-url https://test.pypi.org/legacy/ \
--skip-existing \
-m rust/Cargo.toml
# PyPI (tag de lanzamiento)
MATURIN_PASSWORD="$PYPI_API_TOKEN" maturin publish \
--username __token__ \
--password "$MATURIN_PASSWORD" \
--skip-existing \
-m rust/Cargo.toml
El workflow de CI (.github/workflows/publish.yml) reutiliza estos scripts para construir y verificar ruedas en cada push a release/* o en tags v*, adjunta los artefactos generados y, si hay tokens configurados, publica automáticamente a TestPyPI o PyPI según la referencia.
Integración continua (Fase 1)
El workflow de CI (.github/workflows/ci.yml) se ejecuta en cada push y pull request. Configura Python 3.10 y Rust estable, crea un entorno virtual local, instala las dependencias con pip install -e "[dev]", compila el módulo con python -m maturin develop usando CARGO_TARGET_DIR="$PWD/target" y ejecuta las pruebas pytest tests/test_smoke.py y pytest tests/test_dev_environment_setup.py (esta última usa rutas temporales para no dejar residuos).
Para acelerar los builds, el workflow cachea ~/.cargo y target/ en función del Cargo.lock del crate de Rust. Si quieres reproducir localmente los mismos pasos, ejecuta los comandos anteriores desde un entorno limpio y con Python 3.10+ activado.
Layout
pyrust/— Python package (public API + placeholders)rust/— Rust crate compiled as a Python extension (PyO3)examples/— Examples (will grow)tests/— Tests (will grow)
Project details
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file pyrust_native-0.0.4.tar.gz.
File metadata
- Download URL: pyrust_native-0.0.4.tar.gz
- Upload date:
- Size: 54.9 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.11.9
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
de487945b7f672ea5587d7feea183a74755ecc8204a0cc72414146d466a03127
|
|
| MD5 |
45dcee2bc0ab0ef45335af4d6b50d333
|
|
| BLAKE2b-256 |
13ad0ccd4a402e1eb5ec7a8c29c1a192db1b4f383dcf7d180e9266c073fea07f
|
File details
Details for the file pyrust_native-0.0.4-cp311-cp311-win_amd64.whl.
File metadata
- Download URL: pyrust_native-0.0.4-cp311-cp311-win_amd64.whl
- Upload date:
- Size: 177.4 kB
- Tags: CPython 3.11, Windows x86-64
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.11.9
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
a8887ba512c349fdafc91ca825fdad338786f03d4dbb43fd196e6c054146fdd6
|
|
| MD5 |
8ebbb2089bbb61010fb225aeb232b61c
|
|
| BLAKE2b-256 |
58d1208b118d7b878726c18fd668c2e93d2dc2e734b0ee526a5d2860e96f5fa9
|