Skip to content

Commit d0679c2

Browse files
authored
Merge pull request #19 from Kilo59/aio-files
Aio files
2 parents 703153b + bd03939 commit d0679c2

File tree

6 files changed

+95
-39
lines changed

6 files changed

+95
-39
lines changed

Pipfile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ responses = "*"
2121

2222
[packages]
2323
aiocache = {extras = ["redis"],version = "*"}
24+
aiofiles = "*"
2425
aiohttp = "*"
2526
asyncache = "*"
2627
cachetools = "*"

Pipfile.lock

Lines changed: 27 additions & 18 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

app/io.py

Lines changed: 32 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,56 @@
11
"""app.io.py"""
22
import json
33
import pathlib
4-
from typing import Dict, Union
4+
from typing import Dict, List, Union
5+
6+
import aiofiles
57

68
HERE = pathlib.Path(__file__)
79
DATA = HERE.joinpath("..", "data").resolve()
810

911

1012
def save(
11-
name: str, content: Union[str, Dict], write_mode: str = "w", indent: int = 2, **json_dumps_kwargs
13+
name: str, content: Union[str, Dict, List], write_mode: str = "w", indent: int = 2, **json_dumps_kwargs
1214
) -> pathlib.Path:
1315
"""Save content to a file. If content is a dictionary, use json.dumps()."""
1416
path = DATA / name
15-
if isinstance(content, dict):
17+
if isinstance(content, (dict, list)):
1618
content = json.dumps(content, indent=indent, **json_dumps_kwargs)
1719
with open(DATA / name, mode=write_mode) as f_out:
1820
f_out.write(content)
1921
return path
2022

2123

22-
def load(name: str, **json_kwargs) -> Union[str, Dict]:
24+
def load(name: str, **json_kwargs) -> Union[str, Dict, List]:
2325
"""Loads content from a file. If file ends with '.json', call json.load() and return a Dictionary."""
2426
path = DATA / name
2527
with open(path) as f_in:
2628
if path.suffix == ".json":
2729
return json.load(f_in, **json_kwargs)
2830
return f_in.read()
31+
32+
33+
class AIO:
34+
"""Asynsc compatible file io operations."""
35+
36+
@classmethod
37+
async def save(
38+
cls, name: str, content: Union[str, Dict, List], write_mode: str = "w", indent: int = 2, **json_dumps_kwargs
39+
):
40+
"""Save content to a file. If content is a dictionary, use json.dumps()."""
41+
path = DATA / name
42+
if isinstance(content, (dict, list)):
43+
content = json.dumps(content, indent=indent, **json_dumps_kwargs)
44+
async with aiofiles.open(DATA / name, mode=write_mode) as f_out:
45+
await f_out.write(content)
46+
return path
47+
48+
@classmethod
49+
async def load(cls, name: str, **json_kwargs) -> Union[str, Dict, List]:
50+
"""Loads content from a file. If file ends with '.json', call json.load() and return a Dictionary."""
51+
path = DATA / name
52+
async with aiofiles.open(path) as f_in:
53+
content = await f_in.read()
54+
if path.suffix == ".json":
55+
content = json.loads(content, **json_kwargs)
56+
return content

requirements-dev.txt

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
-i https://pypi.org/simple
22
appdirs==1.4.3
3-
astroid==2.3.3
3+
astroid==2.4.0
44
async-asgi-testclient==1.4.4
55
async-generator==1.10
66
asyncmock==0.4.2
@@ -9,7 +9,7 @@ bandit==1.6.2
99
black==19.10b0
1010
certifi==2020.4.5.1
1111
chardet==3.0.4
12-
click==7.1.1
12+
click==7.1.2
1313
coverage==5.1
1414
coveralls==2.0.0
1515
docopt==0.6.2
@@ -29,7 +29,7 @@ pathspec==0.8.0
2929
pbr==5.4.5
3030
pluggy==0.13.1
3131
py==1.8.1
32-
pylint==2.4.4
32+
pylint==2.5.0
3333
pyparsing==2.4.7
3434
pytest-asyncio==0.11.0
3535
pytest-cov==2.8.1
@@ -42,8 +42,8 @@ six==1.14.0
4242
smmap==3.0.2
4343
stevedore==1.32.0
4444
toml==0.10.0
45-
typed-ast==1.4.1
45+
typed-ast==1.4.1 ; implementation_name == 'cpython' and python_version < '3.8'
4646
urllib3==1.25.9
4747
wcwidth==0.1.9
48-
wrapt==1.11.2
48+
wrapt==1.12.1
4949
zipp==3.1.0

requirements.txt

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
-i https://pypi.org/simple
22
aiocache[redis]==0.11.1
3+
aiofiles==0.5.0
34
aiohttp==3.6.2
45
aioredis==1.3.1
56
async-timeout==3.0.1
@@ -8,7 +9,7 @@ attrs==19.3.0
89
cachetools==4.1.0
910
certifi==2020.4.5.1
1011
chardet==3.0.4
11-
click==7.1.1
12+
click==7.1.2
1213
dataclasses==0.6 ; python_version < '3.7'
1314
fastapi==0.54.1
1415
gunicorn==20.0.4
@@ -25,7 +26,7 @@ requests==2.23.0
2526
six==1.14.0
2627
starlette==0.13.2
2728
urllib3==1.25.9
28-
uvicorn==0.11.3
29+
uvicorn==0.11.4
2930
uvloop==0.14.0 ; sys_platform != 'win32' and sys_platform != 'cygwin' and platform_python_implementation != 'PyPy'
3031
websockets==8.1
3132
yarl==1.4.2

tests/test_io.py

Lines changed: 27 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -5,15 +5,17 @@
55

66
import app.io
77

8-
9-
@pytest.mark.parametrize(
8+
IO_PARAMS = (
109
"name, content, kwargs",
1110
[
1211
("test_file.txt", string.ascii_lowercase, {}),
1312
("test_json_file.json", {"a": 0, "b": 1, "c": 2}, {}),
1413
("test_custom_json.json", {"z": -1, "b": 1, "y": -2, "a": 0}, {"indent": 4, "sort_keys": True}),
1514
],
1615
)
16+
17+
18+
@pytest.mark.parametrize(*IO_PARAMS)
1719
def test_save(tmp_path, name, content, kwargs):
1820
test_path = tmp_path / name
1921
assert not test_path.exists()
@@ -23,17 +25,32 @@ def test_save(tmp_path, name, content, kwargs):
2325
assert test_path.exists()
2426

2527

26-
@pytest.mark.parametrize(
27-
"name, content, kwargs",
28-
[
29-
("test_file.txt", string.ascii_lowercase, {}),
30-
("test_json_file.json", {"a": 0, "b": 1, "c": 2}, {}),
31-
("test_custom_json.json", {"z": -1, "b": 1, "y": -2, "a": 0}, {"indent": 4, "sort_keys": True}),
32-
],
33-
)
28+
@pytest.mark.parametrize(*IO_PARAMS)
3429
def test_round_trip(tmp_path, name, content, kwargs):
3530
test_path = tmp_path / name
3631
assert not test_path.exists()
3732

3833
app.io.save(test_path, content, **kwargs)
3934
assert app.io.load(test_path) == content
35+
36+
37+
@pytest.mark.asyncio
38+
@pytest.mark.parametrize(*IO_PARAMS)
39+
async def test_async_save(tmp_path, name, content, kwargs):
40+
test_path = tmp_path / name
41+
assert not test_path.exists()
42+
43+
result = await app.io.AIO.save(test_path, content, **kwargs)
44+
assert result == test_path
45+
assert test_path.exists()
46+
47+
48+
@pytest.mark.asyncio
49+
@pytest.mark.parametrize(*IO_PARAMS)
50+
async def test_async_round_trip(tmp_path, name, content, kwargs):
51+
test_path = tmp_path / name
52+
assert not test_path.exists()
53+
54+
await app.io.AIO.save(test_path, content, **kwargs)
55+
load_results = await app.io.AIO.load(test_path)
56+
assert load_results == content

0 commit comments

Comments
 (0)