Skip to content

Commit

Permalink
Merge pull request #50 from metricq/docu-doc-delete-db-purge
Browse files Browse the repository at this point in the history
Document document deletion
  • Loading branch information
bmario committed Dec 2, 2022
2 parents 42833f7 + 2d141d9 commit 5a42ab9
Show file tree
Hide file tree
Showing 10 changed files with 51 additions and 23 deletions.
3 changes: 1 addition & 2 deletions aiocouch/bulk.py
Expand Up @@ -137,8 +137,7 @@ def create(self, id: str, data: Optional[JsonDict] = None) -> Document:

return doc

# Mypy isn't smart enough, see: https://github.com/python/mypy/issues/1362
@property # type:ignore
@property
@deprecated(version="2.1.0", reason="Use the response property instead.")
def status(self) -> Optional[List[JsonDict]]: # pragma: no cover
return self.response
Expand Down
2 changes: 1 addition & 1 deletion aiocouch/couchdb.py
Expand Up @@ -126,7 +126,7 @@ async def keys(self, **params: Any) -> List[str]:
async def info(self) -> JsonDict:
"""Returns the meta information about the connected CouchDB server.
See also :ref:`GET /<couchdb:api/server/root>`
See also :external+couchdb:http:get:`/`.
:return: A dict containing the response json.
Expand Down
13 changes: 6 additions & 7 deletions aiocouch/database.py
Expand Up @@ -147,7 +147,7 @@ async def docs(
:param create: If ``True``, every document contained in `ids`, which doesn't
exist, will be represented by an empty
:class:`~aiocouch.document.Document` instance.
:param prefix: Allows to iterator over a subset of documents by specifing a
:param prefix: Allows to iterator over a subset of documents by specifying a
prefix that the documents must match.
:param include_ddocs: Include the design documents of the database.
:param params: Additional query parameters,
Expand Down Expand Up @@ -204,7 +204,7 @@ async def find(
This method allows to use the :ref:`_find<couchdb:api/db/_find>`
endpoint of the database.
This method supports all request paramters listed in
This method supports all request parameters listed in
:ref:`_find<couchdb:api/db/_find>`.
.. note:: As this method returns :class:`~aiocouch.document.Document` s, which
Expand All @@ -227,7 +227,7 @@ async def index(self, index: JsonDict, **kwargs: Any) -> JsonDict:
This method allows to use the :ref:`_index<couchdb:api/db/find/index>`
endpoint of the database.
This method supports all request paramters listed in
This method supports all request parameters listed in
:ref:`_index<couchdb:api/db/find/index>`.
:param index: JSON description of the index
Expand Down Expand Up @@ -318,7 +318,7 @@ async def security(self) -> SecurityDocument:
async def info(self) -> JsonDict:
"""Returns basic information about the database
See also :ref:`GET /db<couchdb:api/db>`.
See also :external+couchdb:http:get:`/{db}`.
:return: Description of returned object.
:rtype: def
Expand All @@ -337,12 +337,11 @@ async def changes(
See also :ref:`/db/_changes<couchdb:api/db/changes>`.
For convinience, the ``last-event-id`` parameter can also be passed
For convenience, the ``last-event-id`` parameter can also be passed
as ``last_event_id``.
"""
if last_event_id and "last-event-id" not in params:
params["last-event-id"] = last_event_id
params.setdefault("last-event-id", last_event_id)

async for json in self._changes(**params):
if "deleted" in json and json["deleted"] is True:
Expand Down
17 changes: 12 additions & 5 deletions aiocouch/document.py
Expand Up @@ -57,7 +57,7 @@ class Document(RemoteDocument):
:param `~aiocouch.database.Database` database: The database of the document
:param id: the id of the document
:param data: the inital data used to set the body of the document
:param data: the initial data used to set the body of the document
"""

Expand Down Expand Up @@ -139,11 +139,18 @@ async def save(self) -> None:
self._update_rev_after_save(data)

async def delete(self, discard_changes: bool = False) -> None:
"""Deletes the document from the server
"""Marks the document as deleted on the server
Calling this method deletes the local data and the document on the server.
Afterwards, the instance can be filled with new data and call :meth:`.save`
again.
Calling this method deletes the local data and marks document as deleted on
the server. Afterwards, the instance can be filled with new data and call
:meth:`.save` again.
.. note::
This method uses the :external+couchdb:http:delete:`/{db}/{docid}`
endpoint.
If you want to remove the data from the server, you'd need to use the
:ref:`_purge<couchdb:api/db/purge>` endpoint instead.
:raises ~aiocouch.ConflictError: if the local data has changed without saving
:raises ~aiocouch.ConflictError: if the local revision is different from the
Expand Down
12 changes: 11 additions & 1 deletion aiocouch/remote.py
Expand Up @@ -189,7 +189,7 @@ async def _info(self) -> JsonDict:
assert not isinstance(json, bytes)
return json

@raises(401, "Authentification failed, check provided credentials.")
@raises(401, "Authentication failed, check provided credentials.")
async def _check_session(self) -> RequestResult:
return await self._get("/_session")

Expand Down Expand Up @@ -309,6 +309,16 @@ async def _changes(self, **params: Any) -> AsyncGenerator[JsonDict, None]:
for result in json["results"]:
yield result

@raises(400, "Invalid database or JSON payload")
@raises(415, "Bad Content-Type header value")
@raises(500, "Internal server error or timeout")
async def _purge(self, docs: JsonDict, **params: Any) -> JsonDict:
_, json = await self._remote._post(
f"{self.endpoint}/_purge", data=docs, params=params
)
assert not isinstance(json, bytes)
return json


class RemoteDocument:
def __init__(self, database: "database.Database", id: str):
Expand Down
8 changes: 4 additions & 4 deletions aiocouch/view.py
Expand Up @@ -91,7 +91,7 @@ def __init__(
super().__init__(database, design_doc, id)

@property
def prefix_sentinal(self) -> str:
def prefix_sentinel(self) -> str:
return "\uffff"

async def get(self, **params: Any) -> ViewResponse:
Expand Down Expand Up @@ -122,7 +122,7 @@ async def ids(
) -> AsyncGenerator[str, None]:
if prefix is not None:
params["startkey"] = f'"{prefix}"'
params["endkey"] = f'"{prefix}{self.prefix_sentinal}"'
params["endkey"] = f'"{prefix}{self.prefix_sentinel}"'

response = await (
self.get(**params) if keys is None else self.post(keys, **params)
Expand All @@ -147,7 +147,7 @@ async def docs(
)

params["startkey"] = f'"{prefix}"'
params["endkey"] = f'"{prefix}{self.prefix_sentinal}"'
params["endkey"] = f'"{prefix}{self.prefix_sentinel}"'

response = await (
self.get(**params) if ids is None else self.post(ids, **params)
Expand All @@ -166,5 +166,5 @@ def endpoint(self) -> str:
return f"/{self._database.id}/_all_docs"

@property
def prefix_sentinal(self) -> str:
def prefix_sentinel(self) -> str:
return chr(0x10FFFE)
1 change: 1 addition & 0 deletions docs/conf.py
Expand Up @@ -53,6 +53,7 @@
"sphinx_autodoc_typehints",
"sphinx.ext.intersphinx",
"sphinxcontrib_trio",
"sphinxcontrib.httpdomain",
]

# Add any paths that contain templates here, relative to this directory.
Expand Down
1 change: 1 addition & 0 deletions pyproject.toml
Expand Up @@ -59,6 +59,7 @@ testpaths = [
"tests"
]
python_files = 'test_*.py'
asyncio_mode = "auto"

[tool.mypy]
warn_return_any = true
Expand Down
5 changes: 3 additions & 2 deletions setup.cfg
Expand Up @@ -48,15 +48,16 @@ examples =
click-completion
tests =
pytest
pytest-asyncio
pytest-asyncio>=0.17
pytest-cov
packaging
docs =
sphinx
sphinx-autodoc-typehints
sphinxcontrib-trio
sphinxcontrib-httpdomain
typing =
mypy
mypy >= 0.981
types-Deprecated
dev =
isort
Expand Down
12 changes: 11 additions & 1 deletion tests/test_database.py
Expand Up @@ -165,7 +165,7 @@ async def test_docs_filtered(filled_database: Database) -> None:
assert "baz" in keys


async def test_docs_on_non_existant(database: Database) -> None:
async def test_docs_on_non_existent(database: Database) -> None:
docs = [doc async for doc in database.docs(["foo"], create=True)]

assert len(docs) == 1
Expand Down Expand Up @@ -247,6 +247,16 @@ async def test_find_limited(filled_database: Database) -> None:
assert "baz2" in matching_keys


async def test_purge(filled_database: Database) -> None:
from aiocouch.exception import NotFoundError

doc = await filled_database["foo"]
await filled_database._purge({doc.id: [doc.rev]})

with pytest.raises(NotFoundError):
await doc.info()


async def test_find_fields_parameter_gets_rejected(database: Database) -> None:
with pytest.raises(ValueError):
[doc async for doc in database.find({"bar": True}, fields="anything")]
Expand Down

0 comments on commit 5a42ab9

Please sign in to comment.