Skip to content

Commit

Permalink
feat(helpers): add as_binaryio helper
Browse files Browse the repository at this point in the history
  • Loading branch information
robinvandernoord committed Sep 28, 2023
1 parent 38ef788 commit 20e4f29
Show file tree
Hide file tree
Showing 2 changed files with 56 additions and 1 deletion.
33 changes: 33 additions & 0 deletions src/configuraptor/helpers.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,15 @@
"""
Contains stand-alone helper functions.
"""
import contextlib
import dataclasses as dc
import io
import math
import os
import types
import typing
from collections import ChainMap
from pathlib import Path

import black.files
from typeguard import TypeCheckError
Expand Down Expand Up @@ -160,3 +163,33 @@ def dataclass_field(cls: Type, key: str) -> typing.Optional[dc.Field[typing.Any]
"""
fields = getattr(cls, "__dataclass_fields__", {})
return fields.get(key)


@contextlib.contextmanager
def uncloseable(fd: typing.BinaryIO) -> typing.Generator[typing.BinaryIO, typing.Any, None]:
"""
Context manager which turns the fd's close operation to no-op for the duration of the context.
"""
close = fd.close
fd.close = lambda: None # type: ignore
yield fd
fd.close = close # type: ignore


def as_binaryio(file: str | Path | typing.BinaryIO | None, mode: typing.Literal["rb", "wb"] = "rb") -> typing.BinaryIO:
"""
Convert a number of possible 'file' descriptions into a single BinaryIO interface.
"""
if isinstance(file, str):
file = Path(file)
if isinstance(file, Path):
file = file.open(mode)
if file is None:
file = io.BytesIO()
if isinstance(file, io.BytesIO):
# so .read() works after .write():
file.seek(0)
# so the with-statement doesn't close the in-memory file:
file = uncloseable(file) # type: ignore

return file
24 changes: 23 additions & 1 deletion tests/test_core.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
# not directly testable
import math
import sys
import io
import typing
from pathlib import Path

import pytest

from src.configuraptor import all_annotations
from src.configuraptor.core import is_optional
from src.configuraptor.helpers import as_binaryio
from src.configuraptor.type_converters import str_to_none


Expand All @@ -22,6 +24,26 @@ def test_is_optional_with_weird_inputs():
assert is_optional(typing.Optional[dict[str, typing.Optional[str]]]) is True


def test_as_binaryio():
path = Path("/tmp/pytest_asbinary_file")
path.touch()

with as_binaryio(str(path)) as f:
assert hasattr(f, "read")

with as_binaryio(path) as f:
assert hasattr(f, "read")

with as_binaryio(open(str(path), "rb")) as f:
assert hasattr(f, "read")

with as_binaryio(io.BytesIO()) as f:
assert hasattr(f, "read")

with as_binaryio(None) as f:
assert hasattr(f, "read")


class Base:
has: int

Expand Down

0 comments on commit 20e4f29

Please sign in to comment.