|
1 | 1 | from __future__ import annotations
|
2 | 2 |
|
| 3 | +import io |
| 4 | +import base64 |
| 5 | +import pathlib |
3 | 6 | from typing import Any, Mapping, TypeVar, cast
|
4 | 7 | from datetime import date, datetime
|
5 | 8 | from typing_extensions import Literal, get_args, override, get_type_hints
|
6 | 9 |
|
| 10 | +import anyio |
7 | 11 | import pydantic
|
8 | 12 |
|
9 | 13 | from ._utils import (
|
10 | 14 | is_list,
|
11 | 15 | is_mapping,
|
12 | 16 | is_iterable,
|
13 | 17 | )
|
| 18 | +from .._files import is_base64_file_input |
14 | 19 | from ._typing import (
|
15 | 20 | is_list_type,
|
16 | 21 | is_union_type,
|
|
29 | 34 | # TODO: ensure works correctly with forward references in all cases
|
30 | 35 |
|
31 | 36 |
|
32 |
| -PropertyFormat = Literal["iso8601", "custom"] |
| 37 | +PropertyFormat = Literal["iso8601", "base64", "custom"] |
33 | 38 |
|
34 | 39 |
|
35 | 40 | class PropertyInfo:
|
@@ -201,6 +206,22 @@ def _format_data(data: object, format_: PropertyFormat, format_template: str | N
|
201 | 206 | if format_ == "custom" and format_template is not None:
|
202 | 207 | return data.strftime(format_template)
|
203 | 208 |
|
| 209 | + if format_ == "base64" and is_base64_file_input(data): |
| 210 | + binary: str | bytes | None = None |
| 211 | + |
| 212 | + if isinstance(data, pathlib.Path): |
| 213 | + binary = data.read_bytes() |
| 214 | + elif isinstance(data, io.IOBase): |
| 215 | + binary = data.read() |
| 216 | + |
| 217 | + if isinstance(binary, str): # type: ignore[unreachable] |
| 218 | + binary = binary.encode() |
| 219 | + |
| 220 | + if not isinstance(binary, bytes): |
| 221 | + raise RuntimeError(f"Could not read bytes from {data}; Received {type(binary)}") |
| 222 | + |
| 223 | + return base64.b64encode(binary).decode("ascii") |
| 224 | + |
204 | 225 | return data
|
205 | 226 |
|
206 | 227 |
|
@@ -323,6 +344,22 @@ async def _async_format_data(data: object, format_: PropertyFormat, format_templ
|
323 | 344 | if format_ == "custom" and format_template is not None:
|
324 | 345 | return data.strftime(format_template)
|
325 | 346 |
|
| 347 | + if format_ == "base64" and is_base64_file_input(data): |
| 348 | + binary: str | bytes | None = None |
| 349 | + |
| 350 | + if isinstance(data, pathlib.Path): |
| 351 | + binary = await anyio.Path(data).read_bytes() |
| 352 | + elif isinstance(data, io.IOBase): |
| 353 | + binary = data.read() |
| 354 | + |
| 355 | + if isinstance(binary, str): # type: ignore[unreachable] |
| 356 | + binary = binary.encode() |
| 357 | + |
| 358 | + if not isinstance(binary, bytes): |
| 359 | + raise RuntimeError(f"Could not read bytes from {data}; Received {type(binary)}") |
| 360 | + |
| 361 | + return base64.b64encode(binary).decode("ascii") |
| 362 | + |
326 | 363 | return data
|
327 | 364 |
|
328 | 365 |
|
|
0 commit comments