-
Notifications
You must be signed in to change notification settings - Fork 1.5k
csv: quote embedded CR/LF under QUOTE_MINIMAL #8315
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -1380,13 +1380,63 @@ mod _csv { | |||||||||||||||||||||||||||||
| self.write.call((s,), vm) | ||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| fn writerow_minimal(&self, row: PyObjectRef, vm: &VirtualMachine) -> PyResult { | ||||||||||||||||||||||||||||||
| let _state = self.state.lock(); | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| let row: ArgIterable = ArgIterable::try_from_object(vm, row.clone()).map_err(|_e| { | ||||||||||||||||||||||||||||||
| new_csv_error( | ||||||||||||||||||||||||||||||
| vm, | ||||||||||||||||||||||||||||||
| format!("'{}' object is not iterable", row.class().name()), | ||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||
| })?; | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| let fields = row.iter(vm)?.collect::<PyResult<Vec<_>>>()?; | ||||||||||||||||||||||||||||||
| let single_field = fields.len() == 1; | ||||||||||||||||||||||||||||||
| let mut output = Vec::new(); | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| for (index, field) in fields.into_iter().enumerate() { | ||||||||||||||||||||||||||||||
| if index > 0 { | ||||||||||||||||||||||||||||||
| output.push(self.dialect.delimiter); | ||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| let stringified; | ||||||||||||||||||||||||||||||
| let data: &[u8] = match_class!(match field { | ||||||||||||||||||||||||||||||
| ref s @ PyStr => s.as_bytes(), | ||||||||||||||||||||||||||||||
| crate::builtins::PyNone => b"", | ||||||||||||||||||||||||||||||
| ref obj => { | ||||||||||||||||||||||||||||||
| stringified = obj.str(vm)?; | ||||||||||||||||||||||||||||||
| stringified.as_bytes() | ||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| // CPython quotes a QUOTE_MINIMAL field if it contains the | ||||||||||||||||||||||||||||||
| // delimiter, the quote character, '\r', '\n', or the line | ||||||||||||||||||||||||||||||
| // terminator, regardless of which line terminator is | ||||||||||||||||||||||||||||||
| // configured. A row with a single empty field is also quoted | ||||||||||||||||||||||||||||||
| // so that it is not read back as an empty line. | ||||||||||||||||||||||||||||||
| if field_needs_quotes(data, self.dialect) || (single_field && data.is_empty()) { | ||||||||||||||||||||||||||||||
| write_quoted_field(&mut output, data, self.dialect, vm)?; | ||||||||||||||||||||||||||||||
| } else { | ||||||||||||||||||||||||||||||
| output.extend_from_slice(data); | ||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| write_lineterminator(&mut output, self.dialect.lineterminator); | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| let s = core::str::from_utf8(&output) | ||||||||||||||||||||||||||||||
| .map_err(|_| vm.new_unicode_decode_error("csv not utf8"))?; | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| self.write.call((s,), vm) | ||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| #[pymethod] | ||||||||||||||||||||||||||||||
| fn writerow(&self, row: PyObjectRef, vm: &VirtualMachine) -> PyResult { | ||||||||||||||||||||||||||||||
| match self.dialect.quoting { | ||||||||||||||||||||||||||||||
| QuoteStyle::None => return self.writerow_quote_none(row, vm), | ||||||||||||||||||||||||||||||
| QuoteStyle::Strings | QuoteStyle::Notnull => { | ||||||||||||||||||||||||||||||
| return self.writerow_quoted_strings(row, vm); | ||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||
| QuoteStyle::Minimal => return self.writerow_minimal(row, vm), | ||||||||||||||||||||||||||||||
|
Comment on lines
1433
to
+1439
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win Update If you adopt the ♻️ Proposed fix- #[pymethod]
- fn writerow(&self, row: PyObjectRef, vm: &VirtualMachine) -> PyResult {
- match self.dialect.quoting {
- QuoteStyle::None => return self.writerow_quote_none(row, vm),
- QuoteStyle::Strings | QuoteStyle::Notnull => {
- return self.writerow_quoted_strings(row, vm);
- }
- QuoteStyle::Minimal => return self.writerow_minimal(row, vm),
- _ => {}
- }
+ #[pymethod]
+ fn writerow(&self, row: PyObjectRef, vm: &VirtualMachine) -> PyResult {
+ if matches!(
+ self.dialect.quoting,
+ QuoteStyle::None | QuoteStyle::Strings | QuoteStyle::Notnull | QuoteStyle::Minimal
+ ) {
+ return self.writerow_custom(row, vm);
+ }📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||
| _ => {} | ||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win
Consolidate duplicated
writerowhelpers into a single method.As per coding guidelines, when branches share common logic, the logic should be extracted to avoid duplication. The new
writerow_minimalis largely a copy ofwriterow_quote_noneandwriterow_quoted_strings(duplicating row iteration, string conversion, and line-terminator logic).Consider extracting a single
writerow_custommethod that handles all handwritten quoting styles and replacing all three redundant helpers.♻️ Proposed unified implementation
Add this unified method, delete
writerow_minimal, and safely remove the now-deadwriterow_quote_noneandwriterow_quoted_stringsmethods:🤖 Prompt for AI Agents
Source: Coding guidelines