Как возвращать данные в формате csv в своем API на Python?

Есть json файл, и есть ручка, в которой нужно выводить его содержимое в csv формате

например

"ip","segment"
"1.1.1.1","google"
"6.6.6.6","hell"

пробовал разные варианты с библиотекой csv, но выдает различные ошибки вывода.

Мой код:

import json
from fastapi import FastAPI
import csv

app = FastAPI() 
data = open('list.json', 'r') 
fieldnames = ['ip', 'type'] 
writer = csv.DictWriter(data, fieldnames=fieldnames) 
list = json.loads(data) 

@app.get("/") 
async def root(): 
    writer.writeheader() 
    for ip in list: 
        writer.writerow(ip) 

Ответы (1 шт):

Автор решения: insolor

Проблемы:

  • json.loads разбирает json из строки, а не из файла. Нужно использовать или json.loads(data.read()) (прочитать файл в строку, потом разобрать данные из строки), или использовать специальную функцию json.load(data)

  • Вы writer инициализируете для записи в тот же файл, из которого вы читаете. Нужно писать в другой файл.

    В целом, для ответа с сервера лучше использовать объект io.StringIO - это объект, который с точки зрения Python ведет себя как файл, но файл фактически нигде не создается, данные хранятся просто в памяти. После записи данных нужно получить значение из него, и вернуть с сервера.

  • Имеет смысл читать из файла и формировать ответ в одном месте - или сразу при старте (снаружи всех функций), тогда сервер всегда будет возвращать одни и те же данные; либо все делать в функции, тогда если файл изменится, и после этого данные будут запрошены, будет возвращен соответствующий новый результат.

  • Результат нужно с сервера возвращать через return, плюс еще обернуть его в объект PlainTextResponse, иначе FastAPI обернет его в JSON-строку

  • Дополнительно в параметрах writer-а нужно указать quoting=csv.QUOTE_ALL, чтобы все значения оборачивались в кавычки, либо quoting=csv.QUOTE_NONNUMERIC - тогда оборачиваться будут только не числовые значения

Итого получается примерно такой код:

Файл JSON

[
    {
        "ip": "1.1.1.1",
        "type": "google"
    },
    {
        "ip": "6.6.6.6",
        "type": "hell"
    }
]

Код сервиса:

import csv
import io
import json

from fastapi import FastAPI
from fastapi.responses import PlainTextResponse

app = FastAPI() 


@app.get("/") 
async def root(): 
    with open('list.json', 'r') as file:
        data = json.load(file)
    
    fieldnames = ['ip', 'type'] 

    csv_in_memory = io.StringIO(newline="")
    writer = csv.DictWriter(
        csv_in_memory,
        fieldnames=fieldnames,
        quoting=csv.QUOTE_ALL
    )

    writer.writeheader() 
    for row in data: 
        writer.writerow(row)
    
    return PlainTextResponse(csv_in_memory.getvalue())

Результат:

Скриншот

Если нужно, чтобы браузер предлагал сохранить файл, то нужно вернуть результат как класс Response, с указанием имени файла в headers и media_type соответствующим типу csv:

@app.get("/") 
async def root(): 
    ...
    
    headers = {"Content-Disposition": "filename=file.csv"}
    return Response(
        csv_in_memory.getvalue(),
        headers=headers,
        media_type="file/csv"
    )

Сохранение файла

→ Ссылка