Skip to main content

ruff_notebook/
schema.rs

1//! The JSON schema of a Jupyter Notebook, entrypoint is [`RawNotebook`]
2//!
3//! Generated by <https://app.quicktype.io/> from
4//! <https://github.com/jupyter/nbformat/blob/16b53251aabf472ad9406ddb1f78b0421c014eeb/nbformat/v4/nbformat.v4.schema.json>
5//! Jupyter Notebook v4.5 JSON schema.
6//!
7//! The following changes were made to the generated version:
8//! * Only keep the required structs and enums.
9//! * `Cell::id` is optional because it wasn't required <v4.5
10//! * `#[serde(deny_unknown_fields)]` was added where the schema had
11//!   `"additionalProperties": false`
12//! * `#[serde(flatten)] pub other: BTreeMap<String, Value>` for
13//!   `"additionalProperties": true` as preparation for round-trip support.
14//! * `#[serde(skip_serializing_if = "Option::is_none")]` was added to optional
15//!   fields where `null` values should not be serialized.
16//! * `Cell::execution_count` is a required property only for code cells, but
17//!   we serialize it for all cells. This is because we can't know if a cell is
18//!   a code cell or not without looking at the `cell_type` property, which
19//!   would require a custom serializer.
20
21use std::collections::{BTreeMap, HashMap};
22
23use serde::{Deserialize, Serialize};
24use serde_json::Value;
25
26fn sort_alphabetically<T: Serialize, S: serde::Serializer>(
27    value: &T,
28    serializer: S,
29) -> Result<S::Ok, S::Error> {
30    let value = serde_json::to_value(value).map_err(serde::ser::Error::custom)?;
31    value.serialize(serializer)
32}
33
34/// This is used to serialize any value implementing [`Serialize`] alphabetically.
35///
36/// The reason for this is to maintain consistency in the generated JSON string,
37/// which is useful for diffing. The default serializer keeps the order of the
38/// fields as they are defined in the struct, which will not be consistent when
39/// there are `extra` fields.
40///
41/// # Example
42///
43/// ```
44/// use std::collections::BTreeMap;
45///
46/// use serde::Serialize;
47///
48/// use ruff_notebook::SortAlphabetically;
49///
50/// #[derive(Serialize)]
51/// struct MyStruct {
52///    a: String,
53///    #[serde(flatten)]
54///    extra: BTreeMap<String, String>,
55///    b: String,
56/// }
57///
58/// let my_struct = MyStruct {
59///     a: "a".to_string(),
60///     extra: BTreeMap::from([
61///         ("d".to_string(), "d".to_string()),
62///         ("c".to_string(), "c".to_string()),
63///     ]),
64///     b: "b".to_string(),
65/// };
66///
67/// let serialized = serde_json::to_string_pretty(&SortAlphabetically(&my_struct)).unwrap();
68/// assert_eq!(
69///     serialized,
70/// r#"{
71///   "a": "a",
72///   "b": "b",
73///   "c": "c",
74///   "d": "d"
75/// }"#
76/// );
77/// ```
78#[derive(Serialize)]
79pub struct SortAlphabetically<T: Serialize>(#[serde(serialize_with = "sort_alphabetically")] pub T);
80
81/// The root of the JSON of a Jupyter Notebook
82///
83/// Generated by <https://app.quicktype.io/> from
84/// <https://github.com/jupyter/nbformat/blob/16b53251aabf472ad9406ddb1f78b0421c014eeb/nbformat/v4/nbformat.v4.schema.json>
85/// Jupyter Notebook v4.5 JSON schema.
86#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
87#[serde(deny_unknown_fields)]
88pub struct RawNotebook {
89    /// Array of cells of the current notebook.
90    pub cells: Vec<Cell>,
91    /// Notebook root-level metadata.
92    pub metadata: RawNotebookMetadata,
93    /// Notebook format (major number). Incremented between backwards incompatible changes to the
94    /// notebook format.
95    pub nbformat: i64,
96    /// Notebook format (minor number). Incremented for backward compatible changes to the
97    /// notebook format.
98    pub nbformat_minor: i64,
99}
100
101/// String identifying the type of cell.
102#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
103#[serde(tag = "cell_type")]
104pub enum Cell {
105    #[serde(rename = "code")]
106    Code(CodeCell),
107    #[serde(rename = "markdown")]
108    Markdown(MarkdownCell),
109    #[serde(rename = "raw")]
110    Raw(RawCell),
111}
112
113/// Notebook raw nbconvert cell.
114#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
115#[serde(deny_unknown_fields)]
116pub struct RawCell {
117    #[serde(skip_serializing_if = "Option::is_none")]
118    pub attachments: Option<Value>,
119    /// Technically, id isn't required (it's not even present) in schema v4.0 through v4.4, but
120    /// it's required in v4.5. Main issue is that pycharm creates notebooks without an id
121    /// <https://youtrack.jetbrains.com/issue/PY-59438/Jupyter-notebooks-created-with-PyCharm-are-missing-the-id-field-in-cells-in-the-.ipynb-json>
122    #[serde(skip_serializing_if = "Option::is_none")]
123    pub id: Option<String>,
124    /// Cell-level metadata.
125    pub metadata: CellMetadata,
126    pub source: SourceValue,
127}
128
129/// Notebook markdown cell.
130#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
131#[serde(deny_unknown_fields)]
132pub struct MarkdownCell {
133    #[serde(skip_serializing_if = "Option::is_none")]
134    pub attachments: Option<Value>,
135    /// Technically, id isn't required (it's not even present) in schema v4.0 through v4.4, but
136    /// it's required in v4.5. Main issue is that pycharm creates notebooks without an id
137    /// <https://youtrack.jetbrains.com/issue/PY-59438/Jupyter-notebooks-created-with-PyCharm-are-missing-the-id-field-in-cells-in-the-.ipynb-json>
138    #[serde(skip_serializing_if = "Option::is_none")]
139    pub id: Option<String>,
140    /// Cell-level metadata.
141    pub metadata: CellMetadata,
142    pub source: SourceValue,
143}
144
145/// Notebook code cell.
146#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
147#[serde(deny_unknown_fields)]
148pub struct CodeCell {
149    /// The code cell's prompt number. Will be null if the cell has not been run.
150    pub execution_count: Option<i64>,
151    /// Technically, id isn't required (it's not even present) in schema v4.0 through v4.4, but
152    /// it's required in v4.5. Main issue is that pycharm creates notebooks without an id
153    /// <https://youtrack.jetbrains.com/issue/PY-59438/Jupyter-notebooks-created-with-PyCharm-are-missing-the-id-field-in-cells-in-the-.ipynb-json>
154    #[serde(skip_serializing_if = "Option::is_none")]
155    pub id: Option<String>,
156    /// Cell-level metadata.
157    pub metadata: CellMetadata,
158    /// Execution, display, or stream outputs.
159    pub outputs: Vec<Value>,
160    pub source: SourceValue,
161}
162
163/// Cell-level metadata.
164#[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq)]
165pub struct CellMetadata {
166    /// VS Code specific cell metadata.
167    ///
168    /// This is [`Some`] only if the cell's preferred language is different from the notebook's
169    /// preferred language.
170    /// <https://github.com/microsoft/vscode/blob/e6c009a3d4ee60f352212b978934f52c4689fbd9/extensions/ipynb/src/serializers.ts#L117-L122>
171    #[serde(skip_serializing_if = "Option::is_none")]
172    pub vscode: Option<CodeCellMetadataVSCode>,
173    /// For additional properties that isn't required by Ruff.
174    #[serde(flatten)]
175    pub extra: HashMap<String, Value>,
176}
177
178/// VS Code specific cell metadata.
179/// <https://github.com/microsoft/vscode/blob/e6c009a3d4ee60f352212b978934f52c4689fbd9/extensions/ipynb/src/serializers.ts#L104-L107>
180#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
181#[serde(rename_all = "camelCase")]
182pub struct CodeCellMetadataVSCode {
183    /// <https://code.visualstudio.com/docs/languages/identifiers>
184    pub language_id: String,
185}
186
187/// Notebook root-level metadata.
188#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Default)]
189pub struct RawNotebookMetadata {
190    /// The author(s) of the notebook document
191    #[serde(skip_serializing_if = "Option::is_none")]
192    pub authors: Option<Value>,
193    /// Kernel information.
194    #[serde(skip_serializing_if = "Option::is_none")]
195    pub kernelspec: Option<Kernelspec>,
196    /// Language information.
197    #[serde(skip_serializing_if = "Option::is_none")]
198    pub language_info: Option<LanguageInfo>,
199    /// Original notebook format (major number) before converting the notebook between versions.
200    /// This should never be written to a file.
201    #[serde(skip_serializing_if = "Option::is_none")]
202    pub orig_nbformat: Option<i64>,
203    /// The title of the notebook document
204    #[serde(skip_serializing_if = "Option::is_none")]
205    pub title: Option<String>,
206    /// For additional properties.
207    #[serde(flatten)]
208    pub extra: BTreeMap<String, Value>,
209}
210
211/// Kernel information.
212#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
213pub struct Kernelspec {
214    /// The language name. This isn't mentioned in the spec but is populated by various tools and
215    /// can be used as a fallback if [`language_info`] is missing.
216    ///
217    /// This is also used by VS Code to determine the preferred language of the notebook:
218    /// <https://github.com/microsoft/vscode/blob/1c31e758985efe11bc0453a45ea0bb6887e670a4/extensions/ipynb/src/deserializers.ts#L20-L22>.
219    ///
220    /// [`language_info`]: RawNotebookMetadata::language_info
221    #[serde(skip_serializing_if = "Option::is_none")]
222    pub language: Option<String>,
223    /// For additional properties that isn't required by Ruff.
224    #[serde(flatten)]
225    pub extra: HashMap<String, Value>,
226}
227
228/// Language information.
229#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
230pub struct LanguageInfo {
231    /// The codemirror mode to use for code in this language.
232    #[serde(skip_serializing_if = "Option::is_none")]
233    pub codemirror_mode: Option<Value>,
234    /// The file extension for files in this language.
235    #[serde(skip_serializing_if = "Option::is_none")]
236    pub file_extension: Option<String>,
237    /// The mimetype corresponding to files in this language.
238    #[serde(skip_serializing_if = "Option::is_none")]
239    pub mimetype: Option<String>,
240    /// The programming language which this kernel runs.
241    pub name: String,
242    /// The pygments lexer to use for code in this language.
243    #[serde(skip_serializing_if = "Option::is_none")]
244    pub pygments_lexer: Option<String>,
245    /// For additional properties.
246    #[serde(flatten)]
247    pub extra: BTreeMap<String, Value>,
248}
249
250/// mimetype output (e.g. text/plain), represented as either an array of strings or a
251/// string.
252///
253/// Contents of the cell, represented as an array of lines.
254///
255/// The stream's text output, represented as an array of strings.
256#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
257#[serde(untagged)]
258pub enum SourceValue {
259    String(String),
260    StringArray(Vec<String>),
261}