Exemple #1
0
async def test_expire():
    resource = memory_resource(key_type=str, value_type=str, expire=0.01)
    await resource["1"].put("foo")
    await resource["1"].get()
    sleep(0.01)
    with pytest.raises(NotFoundError):
        await resource["1"].get()
Exemple #2
0
async def test_clear():
    resource = memory_resource(key_type=str, value_type=str)
    await resource["1"].put("foo")
    await resource["2"].put("bar")
    assert len(await resource.get()) == 2
    await resource.clear()
    assert len(await resource.get()) == 0
Exemple #3
0
async def test_gpdl_bytes():
    resource = memory_resource(key_type=str, value_type=bytes)
    data = b"\x00\x0e\0x01\0x01\0x00"
    id = "binary"
    await resource[id].put(data)
    assert await resource.get() == [id]
    assert await resource[id].get() == data
    data = bytes((1, 2, 3, 4, 5))
    await resource[id].put(data)
    assert await resource[id].get() == data
    await resource[id].delete()
    assert await resource.get() == []
Exemple #4
0
async def test_gpdl_str():
    resource = memory_resource(key_type=str, value_type=str)
    data = "你好,世界!"
    id = "hello_world"
    await resource[id].put(data)
    assert await resource.get() == [id]
    assert await resource[id].get() == data
    data = "Goodbye world!"
    await resource[id].put(data)
    assert await resource[id].get() == data
    await resource[id].delete()
    assert await resource.get() == []
Exemple #5
0
async def test_gpdl_dict():
    DC = make_dataclass("DC", [("foo", str), ("bar", int)])
    resource = memory_resource(key_type=str, value_type=DC)
    id = "id1"
    r1 = DC("hello", 1)
    await resource[id].put(r1)
    r2 = await resource[id].get()
    assert r1 == r2
    r1.bar = 2
    await resource[id].put(r1)
    r2 = await resource[id].get()
    assert r1 == r2
    await resource[id].delete()
    assert await resource.get() == []
Exemple #6
0
def row_resource_class(
    table: Table,
    cache_size: int = 0,
    cache_expire: Union[int, float] = 1,
) -> type:
    """
    Return a base class for a row resource.

    Parameters:
    • table: table for which row resource is based
    • cache_size: number of rows to cache (evict least recently cached)
    • cache_expire: expire time for cached values in seconds
    """

    pk_type = table.columns[table.pk]

    cache = (memory_resource(
        key_type=pk_type,
        value_type=table.schema,
        size=cache_size,
        evict=True,
        expire=cache_expire,
    ) if cache_size else None)

    @resource
    class RowResource:
        """Row resource."""
        def __init__(self, pk: pk_type):
            self.table = table
            self.pk = pk

        async def _validate(self, value: table.schema):
            """Validate value; raise ValidationError if invalid."""
            validate(value, table.schema)

        async def _read(self):
            if cache:
                with suppress(NotFoundError):
                    return await cache[self.pk].get()
            row = await table.read(self.pk)
            if not row:
                raise NotFoundError
            if cache:
                await cache[self.pk].put(row)
            return row

        async def _insert(self, value: table.schema):
            if getattr(value, table.pk) != self.pk:
                raise ValidationError("primary key mismatch")
            await table.insert(value)
            if cache:
                await cache[self.pk].put(value)

        async def _update(self, old: table.schema, new: table.schema):
            if getattr(new, table.pk) != self.pk:
                raise ValidationError("primary key modified")
            if old != new:
                stmt = Statement(f"UPDATE {table.name} SET ")
                updates = []
                for name, python_type in table.columns.items():
                    ofield = getattr(old, name)
                    nfield = getattr(new, name)
                    if ofield != nfield:
                        updates.append(
                            Expression(f"{name} = ",
                                       Param(nfield, python_type)))
                stmt += Expression.join(updates, ", ")
                stmt += Expression(f" WHERE {table.pk} = ",
                                   Param(self.pk, pk_type), ";")
                await table.database.execute(stmt)
            if cache:
                await cache[self.pk].put(new)

        @operation
        async def get(self) -> table.schema:
            """Get row from table."""
            if cache:
                with suppress(NotFoundError):
                    return await cache[self.pk].get()
            async with table.database.transaction():
                row = await table.read(self.pk)
            if not row:
                raise NotFoundError
            if cache:
                await cache[self.pk].put(row)
            return row

        @operation
        async def put(self, value: table.schema):
            """Insert or update (upsert) row."""
            await self._validate(value)
            async with table.database.transaction():
                try:
                    old = await self._read()
                    await self._update(old, value)
                except NotFoundError:
                    await self._insert(value)

        @operation
        async def patch(self, body: dict[str, Any]):
            """Modify row. Patch body is a JSON Merge Patch document."""
            async with table.database.transaction():
                old = await self._read()
                try:
                    new = json_merge_patch(value=old,
                                           type=table.schema,
                                           patch=body)
                except DecodeError as de:
                    raise BadRequestError from de
                await self._validate(new)
                await self._update(old, new)

        @operation
        async def delete(self) -> None:
            """Delete row."""
            if cache:
                with suppress(NotFoundError):
                    await cache[self.pk].delete()
            async with table.database.transaction():
                await table.delete(self.pk)

        @query
        async def exists(self) -> bool:
            """Return if row exists."""
            if cache:
                with suppress(NotFoundError):
                    await cache[self.pk].get()
                    return True
            where = Expression(f"{table.pk} = ",
                               Param(self.pk, table.columns[table.pk]))
            async with table.database.transaction():
                return await table.count(where) != 0

    fondat.types.affix_type_hints(RowResource, localns=locals())
    return RowResource
Exemple #7
0
async def test_size_evict():
    resource = memory_resource(key_type=str, value_type=str, size=2, evict=True)
    await resource["1"].put("foo")
    await resource["2"].put("bar")
    await resource["3"].put("qux")
    assert set(await resource.get()) == {"2", "3"}
Exemple #8
0
async def test_size_limit():
    resource = memory_resource(key_type=str, value_type=str, size=1)
    await resource["1"].put("foo")
    with pytest.raises(InternalServerError):
        await resource["2"].put("bar")
Exemple #9
0
async def test_delete_notfound():
    resource = memory_resource(key_type=str, value_type=str)
    with pytest.raises(NotFoundError):
        await resource["1"].delete()