Skip to content

Commit abfab02

Browse files
authored
feat: add status (#6)
Adds status and status_all methods to the Indexer, along with FileEntry and FileStatus. This allows to query the database after indexing to retrieve potential errors.
1 parent d410404 commit abfab02

File tree

6 files changed

+201
-12
lines changed

6 files changed

+201
-12
lines changed

src/classes.rs

Lines changed: 92 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,9 @@ use stack_graphs::storage::{SQLiteReader, SQLiteWriter};
66
use tree_sitter_stack_graphs::cli::util::{SourcePosition, SourceSpan};
77
use tree_sitter_stack_graphs::loader::Loader;
88

9-
use crate::stack_graphs_wrapper::{index_all, new_loader, query_definition};
9+
use crate::stack_graphs_wrapper::{
10+
get_status, get_status_all, index_all, new_loader, query_definition,
11+
};
1012

1113
#[pyclass]
1214
#[derive(Clone)]
@@ -17,6 +19,77 @@ pub enum Language {
1719
Java,
1820
}
1921

22+
#[pyclass]
23+
#[derive(Clone)]
24+
pub enum FileStatus {
25+
Missing,
26+
Indexed,
27+
Error,
28+
}
29+
30+
#[pyclass]
31+
#[derive(Clone)]
32+
pub struct FileEntry {
33+
#[pyo3(get)]
34+
pub path: String,
35+
#[pyo3(get)]
36+
pub tag: String,
37+
#[pyo3(get)]
38+
pub status: FileStatus,
39+
// As pyo3 does not support string enums, we use Option<String> here instead.
40+
#[pyo3(get)]
41+
pub error: Option<String>,
42+
}
43+
44+
impl From<stack_graphs::storage::FileEntry> for FileEntry {
45+
fn from(entry: stack_graphs::storage::FileEntry) -> Self {
46+
let status = match entry.status {
47+
stack_graphs::storage::FileStatus::Missing => FileStatus::Missing,
48+
stack_graphs::storage::FileStatus::Indexed => FileStatus::Indexed,
49+
stack_graphs::storage::FileStatus::Error(_) => FileStatus::Error,
50+
};
51+
52+
let error = match entry.status {
53+
stack_graphs::storage::FileStatus::Error(e) => Some(e),
54+
_ => None,
55+
};
56+
57+
FileEntry {
58+
path: entry.path.to_str().unwrap().to_string(),
59+
tag: entry.tag,
60+
status,
61+
error,
62+
}
63+
}
64+
}
65+
66+
#[pymethods]
67+
impl FileEntry {
68+
fn __repr__(&self) -> String {
69+
match self {
70+
FileEntry {
71+
path,
72+
tag,
73+
status,
74+
error,
75+
} => {
76+
let error = match error {
77+
Some(e) => format!("(\"{}\")", e),
78+
None => "".to_string(),
79+
};
80+
81+
format!(
82+
"FileEntry(path=\"{}\", tag=\"{}\", status={}{})",
83+
path,
84+
tag,
85+
status.__pyo3__repr__(),
86+
error
87+
)
88+
}
89+
}
90+
}
91+
}
92+
2093
#[pyclass]
2194
#[derive(Clone)]
2295
pub struct Position {
@@ -66,6 +139,7 @@ impl Querier {
66139
#[pyclass]
67140
pub struct Indexer {
68141
db_writer: SQLiteWriter,
142+
db_reader: SQLiteReader,
69143
db_path: String,
70144
loader: Loader,
71145
}
@@ -76,6 +150,7 @@ impl Indexer {
76150
pub fn new(db_path: String, languages: Vec<Language>) -> Self {
77151
Indexer {
78152
db_writer: SQLiteWriter::open(db_path.clone()).unwrap(),
153+
db_reader: SQLiteReader::open(db_path.clone()).unwrap(),
79154
db_path: db_path,
80155
loader: new_loader(languages),
81156
}
@@ -91,8 +166,22 @@ impl Indexer {
91166
}
92167
}
93168

94-
// @TODO: Add a method to retrieve the status of the files (indexed, failed, etc.)
95-
// This might be done on a separate class (Database / Storage), as it is tied to the storage, not a specific indexer
169+
pub fn status(&mut self, paths: Vec<String>) -> PyResult<Vec<FileEntry>> {
170+
let paths: Vec<std::path::PathBuf> =
171+
paths.iter().map(|p| std::path::PathBuf::from(p)).collect();
172+
173+
get_status(paths, &mut self.db_reader)?
174+
.into_iter()
175+
.map(|e| Ok(e.into()))
176+
.collect()
177+
}
178+
179+
pub fn status_all(&mut self) -> PyResult<Vec<FileEntry>> {
180+
get_status_all(&mut self.db_reader)?
181+
.into_iter()
182+
.map(|e| Ok(e.into()))
183+
.collect()
184+
}
96185

97186
fn __repr__(&self) -> String {
98187
format!("Indexer(db_path=\"{}\")", self.db_path)

src/lib.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ use pyo3::prelude::*;
33
mod classes;
44
mod stack_graphs_wrapper;
55

6-
use classes::{Indexer, Language, Position, Querier};
6+
use classes::{FileEntry, FileStatus, Indexer, Language, Position, Querier};
77

88
/// Formats the sum of two numbers as string.
99
#[pyfunction]
@@ -34,6 +34,8 @@ fn stack_graphs_python(_py: Python, m: &PyModule) -> PyResult<()> {
3434
m.add_function(wrap_pyfunction!(index, m)?)?;
3535
m.add_class::<Position>()?;
3636
m.add_class::<Language>()?;
37+
m.add_class::<FileStatus>()?;
38+
m.add_class::<FileEntry>()?;
3739
m.add_class::<Querier>()?;
3840
m.add_class::<Indexer>()?;
3941
Ok(())

src/stack_graphs_wrapper/mod.rs

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,12 @@ pub struct StackGraphsError {
1515
message: String,
1616
}
1717

18+
impl StackGraphsError {
19+
pub fn from(message: String) -> StackGraphsError {
20+
StackGraphsError { message }
21+
}
22+
}
23+
1824
impl std::convert::From<StackGraphsError> for PyErr {
1925
fn from(err: StackGraphsError) -> PyErr {
2026
PyException::new_err(err.message)
@@ -141,3 +147,47 @@ fn canonicalize_paths(paths: Vec<PathBuf>) -> Vec<PathBuf> {
141147
.collect::<std::result::Result<Vec<_>, _>>()
142148
.unwrap()
143149
}
150+
151+
pub fn get_status_all(
152+
db_reader: &mut SQLiteReader,
153+
) -> Result<Vec<stack_graphs::storage::FileEntry>, StackGraphsError> {
154+
let mut files = db_reader
155+
.list_all()
156+
.map_err(|e| StackGraphsError::from(e.to_string()))?;
157+
let iter = files
158+
.try_iter()
159+
.map_err(|e| StackGraphsError::from(e.to_string()))?;
160+
161+
let results = iter
162+
.collect::<Result<Vec<_>, _>>()
163+
.map_err(|e| StackGraphsError::from(e.to_string()))?;
164+
165+
Ok(results)
166+
}
167+
168+
pub fn get_status(
169+
paths: Vec<PathBuf>,
170+
db_reader: &mut SQLiteReader,
171+
) -> Result<Vec<stack_graphs::storage::FileEntry>, StackGraphsError> {
172+
let paths = canonicalize_paths(paths);
173+
174+
let mut entries: Vec<stack_graphs::storage::FileEntry> = Vec::new();
175+
176+
for path in paths {
177+
let mut files = db_reader
178+
.list_file_or_directory(&path)
179+
.map_err(|e| StackGraphsError::from(e.to_string()))?;
180+
181+
let iter = files
182+
.try_iter()
183+
.map_err(|e| StackGraphsError::from(e.to_string()))?;
184+
185+
let results = iter
186+
.collect::<Result<Vec<_>, _>>()
187+
.map_err(|e| StackGraphsError::from(e.to_string()))?;
188+
189+
entries.extend(results)
190+
}
191+
192+
Ok(entries)
193+
}

stack_graphs_python.pyi

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,26 @@ class Language(Enum):
66
TypeScript = 2
77
Java = 3
88

9+
class FileStatus(Enum):
10+
Indexed = 0
11+
Missing = 1
12+
Error = 2
13+
14+
class FileEntry:
15+
"""
16+
An entry in the stack graphs database for a given file:
17+
"""
18+
19+
path: str
20+
tag: str
21+
status: FileStatus
22+
error: str | None
23+
"""
24+
Error message if status is FileStatus.Error
25+
"""
26+
27+
def __repr__(self) -> str: ...
28+
929
class Position:
1030
"""
1131
A position in a given file:
@@ -51,6 +71,22 @@ class Indexer:
5171
Index all the files in the given paths, recursively
5272
"""
5373
...
74+
75+
def status(self, paths: list[str]) -> list[FileEntry]:
76+
"""
77+
Get the status of the given files
78+
- paths: the paths to the files or directories
79+
- returns: a list of FileEntry objects
80+
"""
81+
...
82+
83+
def status_all(self) -> list[FileEntry]:
84+
"""
85+
Get the status of all the files in the database
86+
- returns: a list of FileEntry objects
87+
"""
88+
...
89+
5490
def __repr__(self) -> str: ...
5591

5692
def index(paths: list[str], db_path: str, language: Language) -> None:

tests/js_ok_test.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
from helpers.virtual_files import string_to_virtual_files
2-
from stack_graphs_python import index, Indexer, Querier, Language
2+
from stack_graphs_python import index, Indexer, Querier, Language, FileStatus
33
import os
44

55
code = """
@@ -24,6 +24,10 @@ def test_js_ok():
2424
db_path = os.path.join(dir, "db.sqlite")
2525
indexer = Indexer(db_path, [Language.JavaScript])
2626
indexer.index_all([dir])
27+
status = indexer.status_all()
28+
assert len(status) == 2
29+
assert status[0].path == os.path.join(dir, "index.js")
30+
assert status[0].status == FileStatus.Indexed
2731
querier = Querier(db_path)
2832
source_reference = positions["query"]
2933
results = querier.definitions(source_reference)

tests/ts_ko_test.py

Lines changed: 15 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
from helpers.virtual_files import string_to_virtual_files
2-
from stack_graphs_python import Indexer, Language
2+
from stack_graphs_python import Indexer, Language, FileStatus
33
import os
4-
import pytest
54

65
ok_code = """
76
;---index.ts---
@@ -27,17 +26,26 @@ class A {
2726

2827
def test_ts_ok():
2928
with string_to_virtual_files(ok_code) as (dir, _):
30-
db_path = os.path.abspath("./db.sqlite")
29+
db_path = os.path.join(dir, "db.sqlite")
3130
dir = os.path.abspath(dir)
3231
indexer = Indexer(db_path, [Language.TypeScript])
3332
indexer.index_all([dir])
33+
status = indexer.status_all()
34+
assert len(status) == 1
35+
assert status[0].path == os.path.join(dir, "index.ts")
36+
assert status[0].status == FileStatus.Indexed
3437

3538

36-
@pytest.mark.skip("WIP: add a way to check for errors indexing errors")
3739
def test_ts_ko():
3840
with string_to_virtual_files(ko_code) as (dir, _):
39-
print("here")
40-
db_path = os.path.abspath("./db.sqlite")
41+
db_path = os.path.join(dir, "db.sqlite")
4142
dir = os.path.abspath(dir)
4243
indexer = Indexer(db_path, [Language.TypeScript])
43-
indexer.index_all([dir], db_path, language=Language.TypeScript)
44+
indexer.index_all([dir])
45+
status = indexer.status_all()
46+
assert len(status) == 1
47+
assert status[0].path == os.path.join(dir, "index.ts")
48+
assert status[0].status == FileStatus.Error
49+
assert status[0].error is not None
50+
# TODO(@nohehf): Add logs when we fail to index a file
51+
assert status[0].error != "Error parsing source"

0 commit comments

Comments
 (0)