DriverTrac/venv/lib/python3.12/site-packages/streamlit/elements/pdf.py
2025-11-28 09:08:33 +05:30

191 lines
7.0 KiB
Python

# Copyright (c) Streamlit Inc. (2018-2022) Snowflake Inc. (2022-2025)
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from __future__ import annotations
import io
from pathlib import Path
from typing import TYPE_CHECKING, Any, TypeAlias, cast
from streamlit import url_util
from streamlit.elements.lib.layout_utils import validate_height
from streamlit.errors import StreamlitAPIException
from streamlit.runtime.metrics_util import gather_metrics
if TYPE_CHECKING:
from streamlit.delta_generator import DeltaGenerator
from streamlit.elements.lib.layout_utils import HeightWithoutContent
PdfData: TypeAlias = str | Path | bytes | io.BytesIO
def _get_pdf_component() -> Any | None:
"""Get the PDF custom component if available.
Returns
-------
Any | None
The pdf_viewer function if the streamlit-pdf component is available,
None otherwise.
"""
try:
import streamlit_pdf # type: ignore
return streamlit_pdf.pdf_viewer
except ImportError:
return None
class PdfMixin:
@gather_metrics("pdf")
def pdf(
self,
data: PdfData,
*,
height: HeightWithoutContent = 500,
key: str | None = None,
) -> DeltaGenerator:
"""Display a PDF viewer.
.. Important::
You must install |streamlit-pdf|_ to use this command. You can
install it as an extra with Streamlit:
.. code-block:: shell
pip install streamlit[pdf]
.. |streamlit-pdf| replace:: ``streamlit-pdf``
.. _streamlit-pdf: https://github.com/streamlit/streamlit-pdf
Parameters
----------
data : str, Path, BytesIO, or bytes
The PDF file to show. This can be one of the following:
- A URL (string) for a hosted PDF file.
- A path to a local PDF file. If you use a relative path, it must
be relative to the current working directory.
- A file-like object. For example, this can be an ``UploadedFile``
from ``st.file_uploader``, or this can be a local file opened
with ``open()``.
- Raw bytes data.
height : int or "stretch"
The height of the PDF viewer. This can be one of the following:
- An integer specifying the height in pixels: The viewer has a
fixed height. If the content is larger than the specified
height, scrolling is enabled. This is ``500`` by default.
- ``"stretch"``: The height of the viewer matches the height of
its content or the height of the parent container, whichever is
larger. If the viewer is not in a parent container, the height
of the viewer matches the height of its content.
Example
-------
>>> st.pdf("https://example.com/sample.pdf")
>>> st.pdf("https://example.com/sample.pdf", height=600)
"""
# Validate data parameter early
if data is None:
raise StreamlitAPIException(
"The PDF data cannot be None. Please provide a valid PDF file path, URL, "
"bytes data, or file-like object."
)
# Check if custom PDF component is available first
pdf_component = _get_pdf_component()
if pdf_component is None:
return self._show_pdf_warning()
return self._call_pdf_component(pdf_component, data, height, key)
def _call_pdf_component(
self,
pdf_component: Any,
data: PdfData,
height: HeightWithoutContent,
key: str | None,
) -> DeltaGenerator:
"""Call the custom PDF component with the provided data."""
# Validate height parameter after confirming component is available
validate_height(height, allow_content=False)
# Convert data to the format expected by pdf_viewer component
file_param: str | bytes
if isinstance(data, (str, Path)):
data_str = str(data).strip() # Strip whitespace from URLs
if url_util.is_url(data_str, allowed_schemas=("http", "https")):
# It's a URL - pass directly
file_param = data_str
else:
# It's a local file path - read the content as bytes for security
try:
with open(data_str, "rb") as file:
file_param = file.read()
except (FileNotFoundError, PermissionError) as e:
raise StreamlitAPIException(
f"Unable to read file '{data_str}': {e}"
)
elif isinstance(data, bytes):
# Pass bytes directly - the component will handle uploading to media storage
file_param = data
elif hasattr(data, "read") and hasattr(data, "getvalue"):
# Handle BytesIO and similar
file_param = data.getvalue()
elif hasattr(data, "read"):
# Handle other file-like objects
file_param = data.read()
else:
# Provide a more helpful error message
raise StreamlitAPIException(
f"Unsupported data type for PDF: {type(data).__name__}. "
f"Please provide a file path (str or Path), URL (str), bytes data, "
f"or file-like object (such as BytesIO or UploadedFile)."
)
# Convert to component-compatible format
if height == "stretch":
# For stretch, we need to pass a special value the component understands
# This maintains compatibility with the component while using standard layout
component_height = "stretch"
else:
component_height = str(height)
result = pdf_component(
file=file_param,
height=component_height,
key=key,
)
return cast("DeltaGenerator", result)
def _show_pdf_warning(self) -> DeltaGenerator:
"""Raise an exception that the PDF component is not available."""
raise StreamlitAPIException(
"The PDF viewer requires the `streamlit-pdf` component to be installed.\n\n"
"Please run `pip install streamlit[pdf]` to install it.\n\n"
"For more information, see the Streamlit PDF documentation at "
"https://docs.streamlit.io/develop/api-reference/media/st.pdf."
# TODO: Update this URL when docs are updated
)
@property
def dg(self) -> DeltaGenerator:
"""Get our DeltaGenerator."""
return cast("DeltaGenerator", self)