mirror of
https://github.com/astral-sh/ruff.git
synced 2025-07-28 15:33:50 +00:00
Fallback to kernelspec to check if it's a Python notebook (#12875)
## Summary
This PR adds a fallback logic for `is_python_notebook` to check the
`kernelspec.language` field.
Reference implementation in VS Code:
1c31e75898/extensions/ipynb/src/deserializers.ts (L20-L22)
It's also required for the kernel to provide the `language` they're
implementing based on
https://jupyter-client.readthedocs.io/en/stable/kernels.html#kernel-specs
reference although that's for the `kernel.json` file but is also
included in the notebook metadata.
Closes: #12281
## Test Plan
Add a test case for `is_python_notebook` and include the test notebook
for round trip validation.
The test notebook contains two cells, one is JavaScript (denoted via the
`vscode.languageId` metadata) and the other is Python (no metadata). The
notebook metadata only contains `kernelspec` and the `language_info` is
absent.
I also verified that this is a valid notebook by opening it in Jupyter
Lab, VS Code and using `nbformat` validator.
This commit is contained in:
parent
89c8b49027
commit
2520ebb145
3 changed files with 89 additions and 24 deletions
|
@ -408,13 +408,18 @@ impl Notebook {
|
|||
&self.raw.metadata
|
||||
}
|
||||
|
||||
/// Return `true` if the notebook is a Python notebook, `false` otherwise.
|
||||
/// Check if it's a Python notebook.
|
||||
///
|
||||
/// This is determined by checking the `language_info` or `kernelspec` in the notebook
|
||||
/// metadata. If neither is present, it's assumed to be a Python notebook.
|
||||
pub fn is_python_notebook(&self) -> bool {
|
||||
self.raw
|
||||
.metadata
|
||||
.language_info
|
||||
.as_ref()
|
||||
.map_or(true, |language| language.name == "python")
|
||||
if let Some(language_info) = self.raw.metadata.language_info.as_ref() {
|
||||
return language_info.name == "python";
|
||||
}
|
||||
if let Some(kernel_spec) = self.raw.metadata.kernelspec.as_ref() {
|
||||
return kernel_spec.language.as_deref() == Some("python");
|
||||
}
|
||||
true
|
||||
}
|
||||
|
||||
/// Write the notebook back to the given [`Write`] implementer.
|
||||
|
@ -456,18 +461,12 @@ mod tests {
|
|||
Path::new("./resources/test/fixtures/jupyter").join(path)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_python() -> Result<(), NotebookError> {
|
||||
let notebook = Notebook::from_path(¬ebook_path("valid.ipynb"))?;
|
||||
assert!(notebook.is_python_notebook());
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_r() -> Result<(), NotebookError> {
|
||||
let notebook = Notebook::from_path(¬ebook_path("R.ipynb"))?;
|
||||
assert!(!notebook.is_python_notebook());
|
||||
Ok(())
|
||||
#[test_case("valid.ipynb", true)]
|
||||
#[test_case("R.ipynb", false)]
|
||||
#[test_case("kernelspec_language.ipynb", true)]
|
||||
fn is_python_notebook(filename: &str, expected: bool) {
|
||||
let notebook = Notebook::from_path(¬ebook_path(filename)).unwrap();
|
||||
assert_eq!(notebook.is_python_notebook(), expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
@ -597,9 +596,10 @@ print("after empty cells")
|
|||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn round_trip() {
|
||||
let path = notebook_path("vscode_language_id.ipynb");
|
||||
#[test_case("vscode_language_id.ipynb")]
|
||||
#[test_case("kernelspec_language.ipynb")]
|
||||
fn round_trip(filename: &str) {
|
||||
let path = notebook_path(filename);
|
||||
let expected = std::fs::read_to_string(&path).unwrap();
|
||||
let actual = super::round_trip(&path).unwrap();
|
||||
assert_eq!(actual, expected);
|
||||
|
|
|
@ -169,7 +169,7 @@ pub struct CellMetadata {
|
|||
/// preferred language.
|
||||
/// <https://github.com/microsoft/vscode/blob/e6c009a3d4ee60f352212b978934f52c4689fbd9/extensions/ipynb/src/serializers.ts#L117-L122>
|
||||
pub vscode: Option<CodeCellMetadataVSCode>,
|
||||
/// Catch-all for metadata that isn't required by Ruff.
|
||||
/// For additional properties that isn't required by Ruff.
|
||||
#[serde(flatten)]
|
||||
pub extra: HashMap<String, Value>,
|
||||
}
|
||||
|
@ -190,8 +190,8 @@ pub struct RawNotebookMetadata {
|
|||
/// The author(s) of the notebook document
|
||||
pub authors: Option<Value>,
|
||||
/// Kernel information.
|
||||
pub kernelspec: Option<Value>,
|
||||
/// Kernel information.
|
||||
pub kernelspec: Option<Kernelspec>,
|
||||
/// Language information.
|
||||
pub language_info: Option<LanguageInfo>,
|
||||
/// Original notebook format (major number) before converting the notebook between versions.
|
||||
/// This should never be written to a file.
|
||||
|
@ -206,6 +206,23 @@ pub struct RawNotebookMetadata {
|
|||
/// Kernel information.
|
||||
#[skip_serializing_none]
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
|
||||
pub struct Kernelspec {
|
||||
/// The language name. This isn't mentioned in the spec but is populated by various tools and
|
||||
/// can be used as a fallback if [`language_info`] is missing.
|
||||
///
|
||||
/// This is also used by VS Code to determine the preferred language of the notebook:
|
||||
/// <https://github.com/microsoft/vscode/blob/1c31e758985efe11bc0453a45ea0bb6887e670a4/extensions/ipynb/src/deserializers.ts#L20-L22>.
|
||||
///
|
||||
/// [`language_info`]: RawNotebookMetadata::language_info
|
||||
pub language: Option<String>,
|
||||
/// For additional properties that isn't required by Ruff.
|
||||
#[serde(flatten)]
|
||||
pub extra: HashMap<String, Value>,
|
||||
}
|
||||
|
||||
/// Language information.
|
||||
#[skip_serializing_none]
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
|
||||
pub struct LanguageInfo {
|
||||
/// The codemirror mode to use for code in this language.
|
||||
pub codemirror_mode: Option<Value>,
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue