Skip to content

Commit 71edea3

Browse files
committed
5.7.10 - Prefer 'cat' for async reads
1 parent 404a397 commit 71edea3

File tree

8 files changed

+58
-16
lines changed

8 files changed

+58
-16
lines changed

.github/workflows/python-package.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,7 @@ jobs:
7777
7878
- run: |
7979
mk python-release owner=vkottler \
80-
repo=runtimepy version=5.7.9
80+
repo=runtimepy version=5.7.10
8181
if: |
8282
matrix.python-version == '3.12'
8383
&& matrix.system == 'ubuntu-latest'

README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,11 @@
22
=====================================
33
generator=datazen
44
version=3.1.4
5-
hash=9fbafffa1da655b349a05dfa48addf0b
5+
hash=1e8298e9f6423ee6f9836f5ec04cc3ab
66
=====================================
77
-->
88

9-
# runtimepy ([5.7.9](https://pypi.org/project/runtimepy/))
9+
# runtimepy ([5.7.10](https://pypi.org/project/runtimepy/))
1010

1111
[![python](https://img.shields.io/pypi/pyversions/runtimepy.svg)](https://pypi.org/project/runtimepy/)
1212
![Build Status](https://github.com/vkottler/runtimepy/workflows/Python%20Package/badge.svg)

local/variables/package.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
---
22
major: 5
33
minor: 7
4-
patch: 9
4+
patch: 10
55
entry: runtimepy

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta:__legacy__"
44

55
[project]
66
name = "runtimepy"
7-
version = "5.7.9"
7+
version = "5.7.10"
88
description = "A framework for implementing Python services."
99
readme = "README.md"
1010
requires-python = ">=3.12"

runtimepy/__init__.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
# =====================================
22
# generator=datazen
33
# version=3.1.4
4-
# hash=5d30f53776c61076df3ffeca2fdb30a1
4+
# hash=01934a9c90fba4488654f035acc8b84e
55
# =====================================
66

77
"""
@@ -10,7 +10,7 @@
1010

1111
DESCRIPTION = "A framework for implementing Python services."
1212
PKG_NAME = "runtimepy"
13-
VERSION = "5.7.9"
13+
VERSION = "5.7.10"
1414

1515
# runtimepy-specific content.
1616
METRICS_NAME = "metrics"

runtimepy/net/server/__init__.py

Lines changed: 5 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@
1111
from typing import Any, Optional, TextIO, Union
1212

1313
# third-party
14-
import aiofiles
1514
from vcorelib import DEFAULT_ENCODING
1615
from vcorelib.io import IndentedFileWriter, JsonObject
1716
from vcorelib.paths import Pathlike, find_file, normalize
@@ -26,7 +25,7 @@
2625
from runtimepy.net.server.html import HtmlApp, HtmlApps, get_html, html_handler
2726
from runtimepy.net.server.json import encode_json, json_handler
2827
from runtimepy.net.tcp.http import HttpConnection
29-
from runtimepy.util import normalize_root, path_has_part
28+
from runtimepy.util import normalize_root, path_has_part, read_binary
3029

3130
MIMETYPES_INIT = False
3231

@@ -158,10 +157,9 @@ async def render_markdown_file(
158157
) -> bytes:
159158
"""Render a markdown file as HTML and return the result."""
160159

161-
async with aiofiles.open(path, mode="r") as path_fd:
162-
return self.render_markdown(
163-
await path_fd.read(), response, **kwargs
164-
)
160+
return self.render_markdown(
161+
(await read_binary(path)).decode(), response, **kwargs
162+
)
165163

166164
async def try_file(
167165
self, path: PathMaybeQuery, response: ResponseHeader
@@ -195,8 +193,7 @@ async def try_file(
195193
self.logger.info("Serving '%s' (MIME: %s)", candidate, mime)
196194

197195
# Return the file data.
198-
async with aiofiles.open(candidate, mode="rb") as path_fd:
199-
result = await path_fd.read()
196+
result = await read_binary(candidate)
200197

201198
break
202199

runtimepy/util.py

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,39 @@
33
"""
44

55
# built-in
6+
import asyncio
67
from os import sep
78
from pathlib import Path
8-
from typing import Iterator, Union
9+
from shutil import which
10+
from typing import Iterator, Optional, Union
911

1012
# third-party
13+
import aiofiles
1114
from vcorelib.paths import normalize
1215

1316
ROOT_PATH = Path(sep)
17+
USE_CAT: Optional[bool] = None
18+
19+
20+
async def read_binary(path: Path, use_aiofiles: bool = False) -> bytes:
21+
"""An async wrapper for reading file contents."""
22+
23+
global USE_CAT # pylint: disable=global-statement
24+
if USE_CAT is None:
25+
USE_CAT = which("cat") is not None
26+
27+
# Avoid ballooning a thread pool with one-off reads.
28+
if USE_CAT and not use_aiofiles:
29+
proc = await asyncio.create_subprocess_exec(
30+
"cat", str(path), stdout=asyncio.subprocess.PIPE
31+
)
32+
result, _ = await proc.communicate()
33+
34+
else:
35+
async with aiofiles.open(path, mode="rb") as path_fd:
36+
result = await path_fd.read()
37+
38+
return result
1439

1540

1641
def normalize_root(*src_parts: Union[str, Path]) -> Path:

tests/test_util.py

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
"""
2+
Test the 'util' module.
3+
"""
4+
5+
# third-party
6+
from pytest import mark
7+
8+
# module under test
9+
from runtimepy.util import read_binary
10+
11+
# internal
12+
from tests.resources import resource
13+
14+
15+
@mark.asyncio
16+
async def test_read_binary():
17+
"""Test 'read_binary' invocations."""
18+
19+
assert await read_binary(resource("test.txt"))
20+
assert await read_binary(resource("test.txt"), use_aiofiles=True)

0 commit comments

Comments
 (0)