Skip to content

Commit

Permalink
Feat: add python binding (#640)
Browse files Browse the repository at this point in the history
  • Loading branch information
programskillforverification authored Aug 16, 2024
1 parent 71b69f6 commit 3945cc4
Show file tree
Hide file tree
Showing 8 changed files with 207 additions and 33 deletions.
93 changes: 93 additions & 0 deletions .github/workflows/python.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
on: [push, pull_request, workflow_dispatch]

name: Aderyn-py

jobs:
check:
name: Check
runs-on: ubuntu-latest
steps:
- name: Checkout sources
uses: actions/checkout@v2

- name: Install stable toolchain
uses: actions-rs/toolchain@v1
with:
profile: minimal
toolchain: stable
override: true

- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: "3.11"

- name: Install git submodules
run: |
git submodule update --init --recursive
- name: Run cargo check
uses: actions-rs/cargo@v1
with:
command: check

reports:
name: Check Reports
runs-on: ubuntu-latest
steps:
- name: foundry-toolchain
uses: foundry-rs/[email protected]

- name: Checkout sources
uses: actions/checkout@v2

- name: Install stable toolchain
uses: actions-rs/toolchain@v1
with:
profile: minimal
toolchain: stable
override: true

- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: "3.11"
cache: "pip"

- name: Submodule init
run: |
git submodule update --init --recursive
- uses: actions/checkout@v3

- uses: actions/setup-node@v3
with:
node-version: 20
cache: "npm"

- uses: pnpm/action-setup@v3
with:
version: 8

- uses: bahmutov/npm-install@v1
with:
useLockFile: false
working-directory: tests/2024-05-Sablier/v2-core

- uses: bahmutov/npm-install@v1
with:
useLockFile: false
working-directory: tests/prb-math/

- name: Setup virtual environment
run: |
python -m venv venv
source venv/bin/activate
pip install -r ./aderyn_py/requirements.txt
- name: Run tests
run: |
source venv/bin/activate
cd aderyn_py
maturin develop
pytest tests
66 changes: 49 additions & 17 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions aderyn_driver/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ serde = { version = "1.0.160", features = ["derive"] }
serde_repr = "0.1.12"
cyfrin-foundry-config = { version = "0.2.1" }
toml = "0.8.13"
field_access = "0.1.8"

[dev-dependencies]
criterion = "0.5.1"
Expand Down
3 changes: 2 additions & 1 deletion aderyn_driver/src/driver.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,10 @@ use aderyn_core::{
},
run,
};
use field_access::FieldAccess;
use std::{collections::HashMap, error::Error, path::PathBuf};

#[derive(Clone)]
#[derive(Clone, FieldAccess)]
pub struct Args {
pub root: String,
pub output: String,
Expand Down
5 changes: 3 additions & 2 deletions aderyn_py/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,9 @@ crate-type = ["cdylib"]

[dependencies]
aderyn_driver = { path = "../aderyn_driver", version = "0.1.10" }
field_access = "0.1.8"

[dependencies.pyo3]
version = "0.19.0"
version = "0.22.2"
# "abi3-py38" tells pyo3 (and maturin) to build using the stable ABI with minimum Python version 3.8
features = ["abi3-py37"]
features = ["abi3-py38"]
2 changes: 2 additions & 0 deletions aderyn_py/requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
pytest==8.3.2
maturin==1.7.0
43 changes: 30 additions & 13 deletions aderyn_py/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,34 +1,51 @@
#![allow(unused)]

use aderyn_driver::driver;
use field_access::{FieldAccess, FieldMut};

fn main() {
use pyo3::prelude::*;
use pyo3::types::{PyBool, PyDict};

#[pyfunction]
fn generate_report(root: String, output: String) {
let args = driver::Args {
#[pyo3(signature = (root, output, **py_kwargs))]
fn generate_report(root: String, output: String, py_kwargs: Option<&Bound<'_, PyDict>>) {
let mut args = driver::Args {
root,
output,
src: None, // TODO support this later
no_snippets: false, // TODO support this later
skip_build: false, // TODO support this later
skip_cloc: false, // TODO support this later
path_includes: None, // TODO support this later
path_excludes: None, // TODO support this later
stdout: false, // TODO support this later
skip_update_check: false, // TODO support this later
auditor_mode: false, // TODO support this later
highs_only: false, // TODO support this later
src: None,
no_snippets: false,
skip_build: false,
skip_cloc: false,
path_includes: None,
path_excludes: None,
stdout: false,
skip_update_check: false,
auditor_mode: false,
highs_only: false,
};

if let Some(kwargs) = py_kwargs {
kwargs.iter().for_each(|(py_key, py_value)| {
let rust_key: String = py_key.extract().unwrap();
if py_value.is_instance_of::<PyBool>() {
let rust_value: bool = py_value.extract().unwrap();
args.field_mut(&rust_key).unwrap().replace(rust_value);
} else {
let rust_value: Vec<String> = py_value.extract().unwrap_or_default();
args.field_mut(&rust_key).unwrap().replace(Some(rust_value));
}
})
}

driver::drive(args);
}

/// A Python module implemented in Rust. The name of this function must match
/// the `lib.name` setting in the `Cargo.toml`, else Python will not be able to
/// import the module.
#[pymodule]
fn aderynpy(_py: Python, m: &PyModule) -> PyResult<()> {
fn aderynpy(m: &Bound<'_, PyModule>) -> PyResult<()> {
m.add_function(wrap_pyfunction!(generate_report, m)?)?;

Ok(())
Expand Down
27 changes: 27 additions & 0 deletions aderyn_py/tests/test_generate_report.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import subprocess
import pytest
from aderynpy import generate_report

@pytest.mark.parametrize("root, report", [
("../tests/contract-playground", "../reports/report.md"),
("../tests/2024-05-Sablier", "../reports/sablier-aderyn-toml-nested-root.md"),
("../tests/adhoc-sol-files", "../reports/adhoc-sol-files-report.md"),
("../tests/foundry-nft-f23", "../reports/nft-report.md"),
("../tests/prb-math", "../reports/prb-math-report.md"),
])
def test_generate_report(root, report):
# Define output file path
out_file = f"./{root.split('../')[-1]}-workflow.md"

# Call the generate_report function
generate_report(root, out_file)

# Run the diff command to compare the generated report with the original report
result = subprocess.run(
["diff", str(out_file), report],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
)

# Check if diff command found any differences (result.returncode == 0 means no differences)
assert result.returncode == 1

0 comments on commit 3945cc4

Please sign in to comment.