use anyhow::{Context, Result};
use std::collections::BTreeSet;
use std::fs;
use std::path::{Path, PathBuf};
use walkdir::WalkDir;
pub mod apply;
pub mod fds;
pub mod rel_imports;
pub fn add_pyright_header(root: &Path) -> Result<usize> {
use std::io::Write;
let mut modified = 0usize;
for entry in WalkDir::new(root).into_iter().filter_map(Result::ok) {
let p = entry.path();
if p.is_file() && p.extension().and_then(|s| s.to_str()) == Some("py") {
let name = p.file_name().and_then(|s| s.to_str()).unwrap_or("");
if !name.ends_with("_pb2.py") && !name.ends_with("_pb2_grpc.py") {
continue;
}
let content = fs::read_to_string(p).with_context(|| format!("read {}", p.display()))?;
let header = "# pyright: reportAttributeAccessIssue=false\n# This file is generated by grpcio-tools and may reference grpc.experimental which lacks stubs in types-grpcio.\n";
if content.starts_with(header) {
continue;
}
let mut f = fs::OpenOptions::new()
.write(true)
.truncate(true)
.open(p)
.with_context(|| format!("open {} for write", p.display()))?;
f.write_all(header.as_bytes())?;
f.write_all(content.as_bytes())?;
modified += 1;
}
}
Ok(modified)
}
pub fn create_packages(root: &Path) -> Result<usize> {
let mut dirs: BTreeSet<PathBuf> = BTreeSet::new();
for entry in WalkDir::new(root).into_iter().filter_map(Result::ok) {
let path = entry.path();
if path.is_dir() {
dirs.insert(path.to_path_buf());
}
}
let mut created = 0usize;
for dir in dirs {
let init_py = dir.join("__init__.py");
if !init_py.exists() {
fs::write(&init_py, b"")
.with_context(|| format!("failed to write {}", init_py.display()))?;
created += 1;
}
}
Ok(created)
}
#[cfg(test)]
mod tests {
use super::*;
use std::fs;
use tempfile::tempdir;
#[test]
fn add_pyright_header_to_pb2_files() {
let dir = tempdir().unwrap();
let pb2_file = dir.path().join("service_pb2.py");
let grpc_file = dir.path().join("service_pb2_grpc.py");
let regular_file = dir.path().join("regular.py");
fs::write(&pb2_file, "# Generated code\nclass Service:\n pass\n").unwrap();
fs::write(
&grpc_file,
"# Generated gRPC code\nclass ServiceServicer:\n pass\n",
)
.unwrap();
fs::write(®ular_file, "# Regular Python file\nprint('hello')\n").unwrap();
let modified = add_pyright_header(dir.path()).unwrap();
assert_eq!(modified, 2);
let pb2_content = fs::read_to_string(&pb2_file).unwrap();
assert!(pb2_content.starts_with("# pyright: reportAttributeAccessIssue=false"));
assert!(pb2_content.contains("# Generated code"));
let grpc_content = fs::read_to_string(&grpc_file).unwrap();
assert!(grpc_content.starts_with("# pyright: reportAttributeAccessIssue=false"));
let regular_content = fs::read_to_string(®ular_file).unwrap();
assert!(!regular_content.contains("pyright"));
}
#[test]
fn add_pyright_header_skips_existing() {
let dir = tempdir().unwrap();
let pb2_file = dir.path().join("service_pb2.py");
let existing_content = "# pyright: reportAttributeAccessIssue=false\n# This file is generated by grpcio-tools and may reference grpc.experimental which lacks stubs in types-grpcio.\n# Generated code\n";
fs::write(&pb2_file, existing_content).unwrap();
let modified = add_pyright_header(dir.path()).unwrap();
assert_eq!(modified, 0);
let content = fs::read_to_string(&pb2_file).unwrap();
assert_eq!(content, existing_content); }
#[test]
fn add_pyright_header_nested_directories() {
let dir = tempdir().unwrap();
let nested_dir = dir.path().join("services");
fs::create_dir_all(&nested_dir).unwrap();
let pb2_file = nested_dir.join("api_pb2.py");
fs::write(&pb2_file, "# Generated code\n").unwrap();
let modified = add_pyright_header(dir.path()).unwrap();
assert_eq!(modified, 1);
let content = fs::read_to_string(&pb2_file).unwrap();
assert!(content.starts_with("# pyright: reportAttributeAccessIssue=false"));
}
#[test]
fn create_packages_all_directories() {
let dir = tempdir().unwrap();
let nested_dirs = ["services", "services/api", "services/auth", "common"];
for nested in &nested_dirs {
fs::create_dir_all(dir.path().join(nested)).unwrap();
}
let created = create_packages(dir.path()).unwrap();
assert_eq!(created, 5);
assert!(dir.path().join("__init__.py").exists());
assert!(dir.path().join("services/__init__.py").exists());
assert!(dir.path().join("services/api/__init__.py").exists());
assert!(dir.path().join("services/auth/__init__.py").exists());
assert!(dir.path().join("common/__init__.py").exists());
}
#[test]
fn create_packages_skips_existing() {
let dir = tempdir().unwrap();
let nested_dir = dir.path().join("services");
fs::create_dir_all(&nested_dir).unwrap();
fs::write(nested_dir.join("__init__.py"), "# Existing content").unwrap();
let created = create_packages(dir.path()).unwrap();
assert_eq!(created, 1);
let content = fs::read_to_string(nested_dir.join("__init__.py")).unwrap();
assert_eq!(content, "# Existing content");
let root_content = fs::read_to_string(dir.path().join("__init__.py")).unwrap();
assert_eq!(root_content, "");
}
#[test]
fn create_packages_empty_directory() {
let dir = tempdir().unwrap();
let created = create_packages(dir.path()).unwrap();
assert_eq!(created, 1);
assert!(dir.path().join("__init__.py").exists());
}
#[test]
fn add_pyright_header_file_extension_filtering() {
let dir = tempdir().unwrap();
fs::write(dir.path().join("service_pb2.py"), "# Python file").unwrap();
fs::write(dir.path().join("service_pb2.pyi"), "# Stub file").unwrap();
fs::write(dir.path().join("service_pb2.txt"), "# Text file").unwrap();
fs::write(dir.path().join("service.py"), "# Regular Python").unwrap();
let modified = add_pyright_header(dir.path()).unwrap();
assert_eq!(modified, 1);
let py_content = fs::read_to_string(dir.path().join("service_pb2.py")).unwrap();
assert!(py_content.starts_with("# pyright"));
let pyi_content = fs::read_to_string(dir.path().join("service_pb2.pyi")).unwrap();
assert!(!pyi_content.contains("pyright"));
let regular_content = fs::read_to_string(dir.path().join("service.py")).unwrap();
assert!(!regular_content.contains("pyright"));
}
}