pyronaut create example.micronaut.micronautguide
Download an Excel file in a Pyronaut application
Learn how to download an Excel file with the Micronaut framework.
1. Getting Started
In this guide, we will demonstrate Micronaut file transfer capabilities by creating an application that downloads an Excel file containing a list of books.
2. What you will need
To complete this guide, you will need the following:
-
Some time on your hands
-
GraalPy installed and the Pyronaut CLI available locally
3. Solution
We recommend that you follow the instructions in the next sections and create the application step by step. However, you can go right to the completed example.
-
Download and unzip the source
4. Writing the Application
Create an application using the Pyronaut CLI (Command Line Interface) or Pyronaut Launch
5. Writing the App
5.1. Books
Create a Book dataclass:
from dataclasses import dataclass
from typing import Annotated
from jakarta.validation.constraints import NotBlank
from micronaut.core.annotation import Introspected
@Introspected
@dataclass
class Book:
isbn: Annotated[str, NotBlank]
name: Annotated[str, NotBlank]
Create a protocol to describe Book retrieval.
from typing import Protocol
from .book import Book
class BookRepository(Protocol):
def find_all(self) -> list[Book]:
...
Create a bean which implements the previous interface:
from jakarta.inject import Singleton
from .book import Book
from .book_repository import BookRepository
@Singleton (1)
class BookRepositoryImpl(BookRepository):
def find_all(self) -> list[Book]:
return [
Book("1491950358", "Building Microservices"),
Book("1680502395", "Release It!"),
Book("0321601912", "Continuous Delivery:"),
]
| 1 | Use jakarta.inject.Singleton to designate a class as a singleton. |
5.2. Spreadsheet Library
Add openpyxl==3.1.5 to requirements.txt. Pyronaut installs the https://openpyxl.readthedocs.io/en/stable/openpyxl Python library] with the application.
5.3. Excel Creation
Create a protocol to describe Excel generation:
from typing import Protocol
from .book import Book
class BookExcelService(Protocol):
SHEET_NAME = "Books"
HEADER_ISBN = "Isbn"
HEADER_NAME = "Name"
HEADER_EXCEL_FILE_SUFFIX = ".xlsx"
HEADER_EXCEL_FILE_PREFIX = "books"
HEADER_EXCEL_FILENAME = HEADER_EXCEL_FILE_PREFIX + HEADER_EXCEL_FILE_SUFFIX
def excel_file_from_books(self, book_list: list[Book]) -> bytes: (1)
...
| 1 | The Python service returns the generated XLSX file as bytes. |
Create a bean which generates the Excel file.
from io import BytesIO
from jakarta.inject import Singleton
from openpyxl import Workbook
from openpyxl.styles import Font
from .book import Book
from .book_excel_service import BookExcelService
@Singleton (1)
class BookExcelServiceImpl(BookExcelService):
def excel_file_from_books(self, book_list: list[Book]) -> bytes:
workbook = Workbook()
sheet = workbook.active
sheet.title = self.SHEET_NAME
sheet.append([self.HEADER_ISBN, self.HEADER_NAME])
for cell in sheet[1]:
cell.font = Font(bold=True)
for book in book_list:
sheet.append([book.isbn, book.name])
output = BytesIO()
workbook.save(output)
return output.getvalue()
| 1 | Use jakarta.inject.Singleton to designate a class as a singleton. |
5.4. Controller
6. Views
To use the Thymeleaf Java template engine to render views in a Pyronaut application, add the following dependency on your classpath.
Create a controller:
from micronaut.http.annotation import Controller, Get, Produces
from micronaut.http import HttpResponse
from micronaut.views import View
from .book_excel_service import BookExcelService
from .book_repository import BookRepository
@Controller (1)
class HomeController:
def __init__(
self,
book_repository: BookRepository, (2)
book_excel_service: BookExcelService,
):
self.book_repository = book_repository
self.book_excel_service = book_excel_service
@View("index") (3)
@Get
def index(self) -> dict:
return {}
@Produces("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet")
@Get("/excel") (4)
def excel(self) -> HttpResponse: (5)
data = self.book_excel_service.excel_file_from_books(
self.book_repository.find_all()
)
return HttpResponse.ok(data).header(
"Content-Disposition",
f'attachment; filename="{BookExcelService.HEADER_EXCEL_FILENAME}"',
)
| 1 | The class is defined as a controller with the @Controller annotation mapped to the path /. |
| 2 | Constructor injection |
| 3 | Use @View annotation to specify which template to use to render the response. |
| 4 | You can specify the HTTP verb that a controller action responds to. To respond to a GET request, use io.micronaut.http.annotation.Get |
| 5 | The response includes a Content-Disposition attachment header so the browser downloads the XLSX file. |
The previous controller index method renders a simple view with a link to download the Excel file:
<!DOCTYPE html>
<html>
<head>
<title>Micronaut</title>
</head>
<body>
<p><a href="/excel">Excel</a></p>
</body>
</html>
6.1. Tests
Often, file transfers remain untested in many applications. In this section, you will see how easy it is to test that the file downloads and that the downloaded file contents match our expectations.
Create a test to verify the Excel file is downloaded and the content matches our expectations.
from io import BytesIO
import pytest
import requests
from openpyxl import load_workbook
from pyronaut.test import MicronautTest, micronaut_test_fixture
@pytest.fixture
def my_context(request):
fixture = micronaut_test_fixture(
request,
MicronautTest(environments=["test"], transactional=False),
) (1)
yield fixture
fixture.stop()
@pytest.fixture
def client(my_context):
return requests.with_context(my_context) (2)
def test_books_can_be_downloaded_as_an_excel_file(client):
response = client.get(
"/excel",
headers={
"Accept": "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
},
) (3)
assert response.status_code == 200
assert response.headers["Content-Disposition"] == 'attachment; filename="books.xlsx"'
workbook = load_workbook(BytesIO(response.content)) (4)
sheet = workbook["Books"]
assert sheet["B2"].value == "Building Microservices"
| 1 | Create a Micronaut test fixture for the application under test. |
| 2 | Use requests.with_context(my_context) to call the application under test with a context-bound HTTP client. |
| 3 | Send the request with an Accept header for an Excel file. |
| 4 | Use response.content to retrieve the Excel bytes. |
7. Testing the Application
To run the tests:
pyronaut install
pyronaut validate-config
pyronaut test
8. Running the Application
To run the application, use the pyronaut dev command, which starts the application on port 8080.
9. Next Steps
Read more about Micronaut File Transfers support.
10. License
| All guides are released with an Apache License 2.0 for the code and a Creative Commons Attribution 4.0 license for the writing and media (images). |