Skip to content
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

Feature/add response streams #820

Open
wants to merge 6 commits into
base: 4.0
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
</p>
<p align="center">
<img alt="GitHub Workflow Status" src="https://img.shields.io/github/actions/workflow/status/MasoniteFramework/masonite/pythonapp.yml?branch=develop">
<img src="https://img.shields.io/badge/python-3.7+-blue.svg" alt="Python Version">

<img alt="GitHub release (latest by date including pre-releases)" src="https://img.shields.io/github/v/release/MasoniteFramework/masonite?include_prereleases">
<img src="https://img.shields.io/github/license/MasoniteFramework/masonite.svg" alt="License">
<a href="https://github.com/psf/black"><img alt="Code style: black" src="https://img.shields.io/badge/code%20style-black-000000.svg"></a>
Expand Down Expand Up @@ -32,7 +32,7 @@ Have questions or want to talk? Be sure to join the [Masonite Discord Community]

## Getting Started Quickly

Create and activate a virtual environment and if you have a working Python 3.7+ installation then getting started is as quick as typing
Create and activate a virtual environment and if you have a working Python <= 3.11 installation then getting started is as quick as typing

```bash
pip install masonite
Expand Down
32 changes: 32 additions & 0 deletions src/masonite/response/response.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import mimetypes
from pathlib import Path
from typing import TYPE_CHECKING, Any
import types

if TYPE_CHECKING:
from ..foundation import Application
Expand Down Expand Up @@ -119,6 +120,8 @@ def data(self) -> bytes:
"""Get the response content as bytes."""
if isinstance(self.content, str):
return bytes(self.content, "utf-8")
if isinstance(self.content, types.GeneratorType):
return b"".join(self.content)

return self.content

Expand All @@ -137,6 +140,10 @@ def view(self, view: Any, status: int = 200) -> "bytes|Response":
view, status = view
self.status(status)

if isinstance(view, types.GeneratorType):
self.status(status)
return self

if not self.get_status_code():
self.status(status)

Expand Down Expand Up @@ -218,3 +225,28 @@ def download(self, name: str, location: str, force: bool = False) -> "Response":
data = filelike.read()

return self.view(data)

def stream(self, name: str, location: str, force: bool = True, chunk_size: int = 8192) -> "Response":
"""Set the response as a file download response using streaming."""
self.status(200)

# Set content type and disposition headers
self.header_bag.add(Header("Content-Type", "application/octet-stream"))
self.header_bag.add(Header("Content-Disposition", f'attachment; filename="{name}{Path(location).suffix}"'))

# Get the file size and set the Content-Length header
file_size = Path(location).stat().st_size
self.header_bag.add(Header("Content-Length", str(file_size)))

# Define the generator to stream the file in chunks
def file_generator():
with open(location, "rb") as file:
while True:
chunk = file.read(chunk_size)
if not chunk:
break
yield chunk

# Set the response content as the file generator and return
self.content = file_generator()
return self
Loading