Skip to content

A rust crate for parsing and analyzing the imports within a python package

License

Notifications You must be signed in to change notification settings

Peter554/pyimports

Repository files navigation

pyimports

Note

I've contributed most of this code to grimp. I'd suggest that you look there since grimp is more mature and better maintained.

CI Crates.io Docs

A rust crate for parsing and analyzing the imports within a python package.

Example

A short example (for more information refer to the docs):

use anyhow::Result;
use maplit::{hashmap,hashset};

use pyimports::prelude::*;
use pyimports::package_info::{PackageInfo,PackageItemToken};
use pyimports::imports_info::{ImportsInfo,InternalImportsPathQueryBuilder};

// You shouldn't use `testpackage!`, it just creates a fake python package
// in a temporary directory. It's (unfortunately) included in the public API
// so that it can be used in the doctests.
use pyimports::{testpackage,testutils::TestPackage};

fn main() -> Result<()> {
    let testpackage = testpackage! {
        "__init__.py" => "from testpackage import a, b",
        "a.py" => "from testpackage import b",
        "b.py" => "from testpackage import c, d",
        "c.py" => "from testpackage import d",
        "d.py" => ""
    };
    let package_info = PackageInfo::build(testpackage.path())?;
    let imports_info = ImportsInfo::build(package_info)?;

    let item = |pypath: &str| -> Result<PackageItemToken> {
        Ok(imports_info.package_info().get_item_by_pypath(&pypath.parse()?).unwrap().token())
    };

    let root_pkg = item("testpackage")?;
    let root_init = item("testpackage.__init__")?;
    let a = item("testpackage.a")?;
    let b = item("testpackage.b")?;
    let c = item("testpackage.c")?;
    let d = item("testpackage.d")?;

    assert_eq!(
        imports_info.internal_imports().get_direct_imports(),
        hashmap! {
            root_pkg => hashset!{root_init},
            root_init => hashset!{a, b},
            a => hashset!{b},
            b => hashset!{c, d},
            c => hashset!{d},
            d => hashset!{},
        }
    );

    assert_eq!(
        imports_info.internal_imports().get_items_directly_imported_by(root_init)?,
        hashset! {a, b}
    );

    assert_eq!(
        imports_info.internal_imports().get_items_that_directly_import(d)?,
        hashset! {b, c}
    );

    assert_eq!(
        imports_info.internal_imports().get_downstream_items(root_init)?,
        hashset! {a, b, c, d}
    );

    assert_eq!(
        imports_info.internal_imports().find_path(
            &InternalImportsPathQueryBuilder::default()
                .from(root_init)
                .to(d)
                .build()?
        )?,
        Some(vec![root_init, b, d])
    );

    Ok(())
}

Scope

This crate might be useful for something eventually, but right now it's mainly just a hobby project for me to learn about rust.

If you are looking for something more mature, try grimp/import-linter.

Limitations

The python parser used within this crate does not currently support python 3.12+ - see the related GitHub issue here.

Next steps

Some possible next steps that I may explore if/when I get time:

  • Fix issue with python parser, to support python 3.12+.
  • Performance benchmarking/improvements.
  • Python bindings (via maturin).
  • Higher level features e.g. import contracts, similar to import-linter.
  • Faster path calculations (via e.g. fast_paths).

About

A rust crate for parsing and analyzing the imports within a python package

Resources

License

Stars

Watchers

Forks

Languages