Initial work on a pathlib clone
This commit is contained in:
parent
35e94363d0
commit
0e2ed77960
|
@ -1,4 +1,4 @@
|
||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<project version="4">
|
<project version="4">
|
||||||
<component name="ProjectRootManager" version="2" project-jdk-name="Python 3.10 (StructuredIO)" project-jdk-type="Python SDK" />
|
<component name="ProjectRootManager" version="2" project-jdk-name="Python 3.10 (structio)" project-jdk-type="Python SDK" />
|
||||||
</project>
|
</project>
|
|
@ -56,7 +56,7 @@ class AsyncResourceWrapper(AsyncResource):
|
||||||
self._file = f
|
self._file = f
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def handle(self):
|
def handle(self) -> io.IOBase:
|
||||||
"""
|
"""
|
||||||
Returns the underlying (synchronous!) OS-specific
|
Returns the underlying (synchronous!) OS-specific
|
||||||
handle for the given resource
|
handle for the given resource
|
||||||
|
|
|
@ -0,0 +1,156 @@
|
||||||
|
# Async wrapper for pathlib.Path (blocking calls are run in threads)
|
||||||
|
import os
|
||||||
|
from functools import partial, wraps
|
||||||
|
import structio
|
||||||
|
import pathlib
|
||||||
|
from structio.core.syscalls import checkpoint
|
||||||
|
|
||||||
|
|
||||||
|
_SYNC = {"as_posix",
|
||||||
|
"as_uri",
|
||||||
|
"is_absolute",
|
||||||
|
"is_reserved",
|
||||||
|
"joinpath",
|
||||||
|
"match",
|
||||||
|
"relative_to",
|
||||||
|
"with_name",
|
||||||
|
"with_suffix"}
|
||||||
|
_ASYNC = {"chmod",
|
||||||
|
"exists",
|
||||||
|
"expanduser",
|
||||||
|
"glob",
|
||||||
|
"group",
|
||||||
|
"is_block_device",
|
||||||
|
"is_char_device",
|
||||||
|
"is_dir",
|
||||||
|
"is_fifo",
|
||||||
|
"is_file",
|
||||||
|
"is_mount",
|
||||||
|
"is_socket",
|
||||||
|
"is_symlink",
|
||||||
|
"lchmod",
|
||||||
|
"lstat",
|
||||||
|
"mkdir",
|
||||||
|
"owner",
|
||||||
|
"read_bytes",
|
||||||
|
"read_text",
|
||||||
|
"rename",
|
||||||
|
"replace",
|
||||||
|
"resolve",
|
||||||
|
"rglob",
|
||||||
|
"rmdir",
|
||||||
|
"samefile",
|
||||||
|
"stat",
|
||||||
|
"symlink_to",
|
||||||
|
"touch",
|
||||||
|
"unlink",
|
||||||
|
"write_text",
|
||||||
|
"write_bytes"
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def _wrap(v):
|
||||||
|
if isinstance(v, pathlib.Path):
|
||||||
|
return Path(v)
|
||||||
|
return v
|
||||||
|
|
||||||
|
|
||||||
|
class Path:
|
||||||
|
"""
|
||||||
|
A wrapper to pathlib.Path which executes
|
||||||
|
blocking calls using structio.thread.run_in_worker
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, *args):
|
||||||
|
self._sync_path: pathlib.Path = pathlib.Path(*args)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
@wraps(pathlib.Path.cwd)
|
||||||
|
async def cwd(*args, **kwargs):
|
||||||
|
"""
|
||||||
|
Like pathlib.Path.cwd(), but async
|
||||||
|
"""
|
||||||
|
|
||||||
|
# This method is special and can't be just forwarded like the others because
|
||||||
|
# it is a class method and I don't feel like doing all the wild metaprogramming
|
||||||
|
# stuff that Trio did (which is cool but gooood luck debugging that), so here ya go.
|
||||||
|
return _wrap(await structio.thread.run_in_worker(pathlib.Path.cwd, *args, **kwargs))
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
@wraps(pathlib.Path.home)
|
||||||
|
async def home(*args, **kwargs):
|
||||||
|
"""
|
||||||
|
Like pathlib.Path.home(), but async
|
||||||
|
"""
|
||||||
|
|
||||||
|
return _wrap(await structio.thread.run_in_worker(pathlib.Path.home, *args, **kwargs))
|
||||||
|
|
||||||
|
@wraps(pathlib.Path.open)
|
||||||
|
async def open(self, *args, **kwargs):
|
||||||
|
"""
|
||||||
|
Like pathlib.Path.open(), but async
|
||||||
|
"""
|
||||||
|
|
||||||
|
f = await structio.thread.run_in_worker(self._sync_path.open, *args, **kwargs)
|
||||||
|
return structio.wrap_file(f)
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return f"structio.Path({repr(str(self._sync_path))})"
|
||||||
|
|
||||||
|
def __dir__(self):
|
||||||
|
return super().__dir__()
|
||||||
|
|
||||||
|
async def iterdir(self):
|
||||||
|
"""
|
||||||
|
Like pathlib.Path.iterdir(), but async
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Inspired by https://github.com/python-trio/trio/issues/501#issuecomment-381724137
|
||||||
|
func = partial(os.listdir, self._sync_path)
|
||||||
|
files = await structio.thread.run_in_worker(func)
|
||||||
|
|
||||||
|
async def agen():
|
||||||
|
if not files:
|
||||||
|
await checkpoint()
|
||||||
|
for name in files:
|
||||||
|
yield self._sync_path._make_child_relpath(name)
|
||||||
|
await checkpoint()
|
||||||
|
|
||||||
|
return agen()
|
||||||
|
|
||||||
|
def __fspath__(self):
|
||||||
|
return os.fspath(self._wrapped)
|
||||||
|
|
||||||
|
def __getattr__(self, attr: str):
|
||||||
|
# We use a similar trick to the one we stole from
|
||||||
|
# Trio for async files, except we also wrap sync
|
||||||
|
# methods because we want them to return our own
|
||||||
|
# Path objects, not pathlib.Path!
|
||||||
|
|
||||||
|
if attr in _SYNC:
|
||||||
|
# We duplicate the code here because we only
|
||||||
|
# want to forward the stuff in _SYNC and _ASYNC,
|
||||||
|
# not everything (like our cwd() classmethod above)
|
||||||
|
m = getattr(self._sync_path, attr)
|
||||||
|
|
||||||
|
@wraps(m)
|
||||||
|
def wrapper(*args, **kwargs):
|
||||||
|
return _wrap(m(*args, **kwargs))
|
||||||
|
|
||||||
|
setattr(self, attr, wrapper)
|
||||||
|
return wrapper
|
||||||
|
if attr in _ASYNC:
|
||||||
|
m = getattr(self._sync_path, attr)
|
||||||
|
|
||||||
|
@wraps(m)
|
||||||
|
async def wrapper(*args, **kwargs):
|
||||||
|
f = partial(m, *args, **kwargs)
|
||||||
|
return _wrap(await structio.thread.run_in_worker(f))
|
||||||
|
|
||||||
|
setattr(self, attr, wrapper)
|
||||||
|
return wrapper
|
||||||
|
# Falls down to __getattribute__, which may find a cached
|
||||||
|
# method we generated earlier!
|
||||||
|
raise AttributeError(attr)
|
||||||
|
|
||||||
|
|
|
@ -18,10 +18,15 @@ async def main_2(data: bytes):
|
||||||
await f.write(data)
|
await f.write(data)
|
||||||
await f.seek(0)
|
await f.seek(0)
|
||||||
assert await f.read(len(data)) == data
|
assert await f.read(len(data)) == data
|
||||||
|
await f.flush()
|
||||||
print(f"[main] Deleting {f.name!r}")
|
print(f"[main] Deleting {f.name!r}")
|
||||||
await structio.thread.run_in_worker(os.unlink, f.name)
|
await structio.thread.run_in_worker(os.unlink, f.name)
|
||||||
|
assert not await structio.thread.run_in_worker(os.path.isfile, f.name)
|
||||||
print(f"[main] Done in {structio.clock() - t:.2f} seconds")
|
print(f"[main] Done in {structio.clock() - t:.2f} seconds")
|
||||||
|
|
||||||
|
|
||||||
structio.run(main)
|
#structio.run(main)
|
||||||
structio.run(main_2, b"a" * 10000)
|
MB = 1_048_576
|
||||||
|
GB = 1
|
||||||
|
# Write 1GB of data (too much?)
|
||||||
|
structio.run(main_2, b"a" * (GB * 1024 * MB))
|
||||||
|
|
Loading…
Reference in New Issue