Skip to content
This repository was archived by the owner on Apr 19, 2025. It is now read-only.

Improve stores #86

Open
wants to merge 17 commits into
base: main
Choose a base branch
from
2 changes: 2 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ classifiers = [
"Programming Language :: Python :: 3.11",
]
dependencies = [
"deprecated",
"anyio >=3.6.2,<5",
"aiosqlite >=0.18.0,<1",
"y-py >=0.6.0,<0.7.0",
Expand All @@ -42,6 +43,7 @@ test = [
"pytest-asyncio",
"websockets >=10.0",
"uvicorn",
"types-Deprecated"
]
docs = [
"mkdocs",
Expand Down
288 changes: 288 additions & 0 deletions tests/test_file_store.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,288 @@
import struct
import time
from pathlib import Path

import anyio
import pytest

from ypy_websocket.stores import DocExists, FileYStore
from ypy_websocket.yutils import Decoder, write_var_uint


@pytest.fixture
def create_store():
async def _inner(path: str, version: int) -> None:
await anyio.Path(path).mkdir(parents=True, exist_ok=True)
version_path = Path(path, "__version__")
async with await anyio.open_file(version_path, "wb") as f:
version_bytes = str(version).encode()
await f.write(version_bytes)

return _inner


@pytest.fixture
def add_document():
async def _inner(path: str, doc_path: str, version: int, data: bytes = b"") -> None:
file_path = Path(path, (doc_path + ".y"))
await anyio.Path(file_path.parent).mkdir(parents=True, exist_ok=True)

async with await anyio.open_file(file_path, "ab") as f:
version_bytes = f"VERSION:{version}\n".encode()
await f.write(version_bytes)
data_len = write_var_uint(len(data))
await f.write(data_len + data)
metadata = b""
metadata_len = write_var_uint(len(metadata))
await f.write(metadata_len + metadata)
timestamp = struct.pack("<d", time.time())
timestamp_len = write_var_uint(len(timestamp))
await f.write(timestamp_len + timestamp)

return _inner


@pytest.mark.anyio
async def test_initialization(tmp_path):
path = tmp_path / "tmp"
store = FileYStore(str(path))
await store.start()
await store.initialize()
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you explain what is the difference between start and initialize?
From what I can see, start now does nothing?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The initialize is used to create or ensure all the resources needed are available before using the store. I moved it out of start because the entity that calls it should be the one deciding whether to call it and forget about it or wait until it finishes.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It seems to me that the new initialize is the old start. What is start used for now?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To create the task group because I saw that some other classes are adding tasks there.


assert store.initialized

version_path = Path(path / "__version__")
async with await anyio.open_file(version_path, "rb") as f:
version = int(await f.readline())
assert FileYStore.version == version


@pytest.mark.anyio
async def test_initialization_with_old_store(tmp_path, create_store):
path = tmp_path / "tmp"

# Create a store with an old version
await create_store(path, 1)

store = FileYStore(str(path))
await store.start()
await store.initialize()

assert store.initialized

version_path = Path(path / "__version__")
async with await anyio.open_file(version_path, "rb") as f:
version = int(await f.readline())
assert FileYStore.version == version


@pytest.mark.anyio
async def test_initialization_with_existing_store(tmp_path, create_store, add_document):
path = tmp_path / "tmp"
doc_path = "test.txt"

# Create a store with an old version
await create_store(path, FileYStore.version)
await add_document(path, doc_path, 0)

store = FileYStore(str(path))
await store.start()
await store.initialize()

assert store.initialized

version_path = Path(path / "__version__")
async with await anyio.open_file(version_path, "rb") as f:
version = int(await f.readline())
assert FileYStore.version == version

file_path = Path(path / (doc_path + ".y"))
assert await anyio.Path(file_path).exists()


@pytest.mark.anyio
async def test_exists(tmp_path, create_store, add_document):
path = tmp_path / "tmp"
doc_path = "test.txt"

# Create a store with an old version
await create_store(path, FileYStore.version)
await add_document(path, doc_path, 0)

store = FileYStore(str(path))
await store.start()
await store.initialize()

assert store.initialized

assert await store.exists(doc_path)

assert not await store.exists("random.path")


@pytest.mark.anyio
async def test_list(tmp_path, create_store, add_document):
path = tmp_path / "tmp"
doc1 = "test_1.txt"
doc2 = "path/to/dir/test_2.txt"

# Create a store with an old version
await create_store(path, FileYStore.version)
await add_document(path, doc1, 0)
await add_document(path, doc2, 0)

store = FileYStore(str(path))
await store.start()
await store.initialize()

assert store.initialized

count = 0
async for doc in store.list():
count += 1
# assert doc in [doc1, doc2]

assert count == 2


@pytest.mark.anyio
async def test_get(tmp_path, create_store, add_document):
path = tmp_path / "tmp"
doc_path = "test.txt"

# Create a store with an old version
await create_store(path, FileYStore.version)
await add_document(path, doc_path, 0)

store = FileYStore(str(path))
await store.start()
await store.initialize()

assert store.initialized

res = await store.get(doc_path)
assert res["path"] == doc_path
assert res["version"] == 0

res = await store.get("random.doc")
assert res is None


@pytest.mark.anyio
async def test_create(tmp_path, create_store, add_document):
path = tmp_path / "tmp"
doc_path = "test.txt"

# Create a store with an old version
await create_store(path, FileYStore.version)
await add_document(path, doc_path, 0)

store = FileYStore(str(path))
await store.start()
await store.initialize()

assert store.initialized

new_doc = "new_doc.path"
await store.create(new_doc, 0)

file_path = Path(path / (new_doc + ".y"))
async with await anyio.open_file(file_path, "rb") as f:
header = await f.read(8)
assert header == b"VERSION:"

version = int(await f.readline())
assert version == 0

with pytest.raises(DocExists) as e:
await store.create(doc_path, 0)
assert str(e.value) == f"The document {doc_path} already exists."


@pytest.mark.anyio
async def test_remove(tmp_path, create_store, add_document):
path = tmp_path / "tmp"
doc_path = "test.txt"

# Create a store with an old version
await create_store(path, FileYStore.version)
await add_document(path, doc_path, 0)

store = FileYStore(str(path))
await store.start()
await store.initialize()

assert store.initialized

await store.remove(doc_path)
assert not await store.exists(doc_path)

new_doc = "new_doc.path"
assert not await store.exists(new_doc)

await store.remove(new_doc)
assert not await store.exists(new_doc)


@pytest.mark.anyio
async def test_read(tmp_path, create_store, add_document):
path = tmp_path / "tmp"
doc_path = "test.txt"
update = b"foo"

# Create a store with an old version
await create_store(path, FileYStore.version)
await add_document(path, doc_path, 0, update)

store = FileYStore(str(path))
await store.start()
await store.initialize()

assert store.initialized

count = 0
async for u, _, _ in store.read(doc_path):
count += 1
assert update == u

assert count == 1


@pytest.mark.anyio
async def test_write(tmp_path, create_store, add_document):
path = tmp_path / "tmp"
doc_path = "test.txt"

# Create a store with an old version
await create_store(path, FileYStore.version)
await add_document(path, doc_path, 0)

store = FileYStore(str(path))
await store.start()
await store.initialize()

assert store.initialized

update = b"foo"
await store.write(doc_path, update)

file_path = Path(path / (doc_path + ".y"))
async with await anyio.open_file(file_path, "rb") as f:
header = await f.read(8)
assert header == b"VERSION:"

version = int(await f.readline())
assert version == 0

count = 0
data = await f.read()

i = 0
for u in Decoder(data).read_messages():
if i == 0:
count += 1
# The fixture add_document inserts an empty update
assert u in [b"", update]
i = (i + 1) % 3

assert count == 2
Loading