aiocypher

A limited asyncio wrapper for various cypher drivers

This is not intended to be a full driver wrapper, but just to implement the things we immediately need in a relatively simple fashion, replicating the interface of the wrapped library where possible.

 1#
 2#
 3# Copyright 2020-21 British Broadcasting Corporation
 4#
 5# Licensed under the Apache License, Version 2.0 (the "License");
 6# you may not use this file except in compliance with the License.
 7# You may obtain a copy of the License at
 8#
 9#     http://www.apache.org/licenses/LICENSE-2.0
10#
11# Unless required by applicable law or agreed to in writing, software
12# distributed under the License is distributed on an "AS IS" BASIS,
13# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14# See the License for the specific language governing permissions and
15# limitations under the License.
16#
17
18"""A limited asyncio wrapper for various cypher drivers
19
20This is not intended to be a full driver wrapper, but just to implement the things
21we immediately need in a relatively simple fashion, replicating the interface of the wrapped
22library where possible.
23"""
24
25from .interface.driver import Driver
26from .interface.session import Session
27from .interface.transaction import Transaction
28from .interface.result import Result
29from .interface.relationship import Relationship
30from .interface.node import Node
31from .interface.exceptions import QueryFailed
32from .empty import EmptyResult
33from .config import Config
34
35try:
36    from . import aioneo4j  # noqa: F401
37except ImportError:
38    pass
39
40
41try:
42    from . import aioagensgraph  # noqa: F401
43except ImportError:
44    pass
45
46
47__all__ = [
48    'Driver',
49    'Session',
50    'Transaction',
51    'Result',
52    'Relationship',
53    'Node',
54    'EmptyResult',
55    'QueryFailed',
56    'Config'
57]
class Driver:
29class Driver (object, metaclass=AIOCypherABCMeta):
30    """This class encapsulates a driver object for a cypher database. Concrete subclasses specialise in
31    different databases.
32    """
33    def __init__(self,
34                 config: Config):
35        self._address = config.address
36        self._auth: Tuple[str, str] = config.auth
37
38    async def __aenter__(self) -> 'Driver':
39        return self
40
41    async def __aexit__(self, e_t, e_v, e_tb) -> Optional[bool]:
42        await self.close()
43        return False
44
45    @abstractmethod
46    def __await__(self):
47        ...
48
49    @abstractmethod
50    async def close(self) -> None:
51        """This coroutine should be awaited when the driver is no longer needed.
52        """
53        ...
54
55    @abstractmethod
56    def session(self, **kwargs) -> AsyncContextManager[Session]:
57        """This method is used to create a Session object, which can be used as an
58        asynchronous context manager.
59
60        All interactions should be done via a Session object. Accessing the driver
61        without one is not supported.
62        """
63        ...
64
65    @abstractmethod
66    def database_type(self) -> str:
67        ...

This class encapsulates a driver object for a cypher database. Concrete subclasses specialise in different databases.

@abstractmethod
async def close(self) -> None:
49    @abstractmethod
50    async def close(self) -> None:
51        """This coroutine should be awaited when the driver is no longer needed.
52        """
53        ...

This coroutine should be awaited when the driver is no longer needed.

@abstractmethod
def session( self, **kwargs) -> AsyncContextManager[Session]:
55    @abstractmethod
56    def session(self, **kwargs) -> AsyncContextManager[Session]:
57        """This method is used to create a Session object, which can be used as an
58        asynchronous context manager.
59
60        All interactions should be done via a Session object. Accessing the driver
61        without one is not supported.
62        """
63        ...

This method is used to create a Session object, which can be used as an asynchronous context manager.

All interactions should be done via a Session object. Accessing the driver without one is not supported.

@abstractmethod
def database_type(self) -> str:
65    @abstractmethod
66    def database_type(self) -> str:
67        ...
class Session:
25class Session (object, metaclass=ABCMeta):
26    """This object encapsulates a Session object. It should not be initialised directly, but
27    created via a Driver object.
28
29    It should always be used as an Async Context Manager, and using its methods outside of its
30    context may lead to undefined behaviour.
31
32    A Session object is the only supported way to interact with the cypher database.
33    """
34    async def __aenter__(self) -> 'Session':
35        return self
36
37    async def __aexit__(self, *args) -> bool:
38        return False
39
40    @abstractmethod
41    def begin_transaction(self) -> AsyncContextManager[Transaction]:
42        """This is the main method for interacting with the Session. It
43        creates a new Transaction which is then represented by an Async Context Manager
44        """
45        ...

This object encapsulates a Session object. It should not be initialised directly, but created via a Driver object.

It should always be used as an Async Context Manager, and using its methods outside of its context may lead to undefined behaviour.

A Session object is the only supported way to interact with the cypher database.

@abstractmethod
def begin_transaction(self) -> AsyncContextManager[Transaction]:
40    @abstractmethod
41    def begin_transaction(self) -> AsyncContextManager[Transaction]:
42        """This is the main method for interacting with the Session. It
43        creates a new Transaction which is then represented by an Async Context Manager
44        """
45        ...

This is the main method for interacting with the Session. It creates a new Transaction which is then represented by an Async Context Manager

class Transaction:
24class Transaction (object, metaclass=ABCMeta):
25    """A class which encapsulates a Transaction. Should never be created directly,
26    but always via a Session object.
27
28    Should always be used as an Async Context Manager. Using any of its methods outside of
29    the context may lead to undefined behaviour.
30    """
31    async def __aenter__(self) -> "Transaction":
32        return self
33
34    async def __aexit__(self, *args):
35        return False
36
37    @abstractmethod
38    def run(self, *args, **kwargs) -> Result:
39        """This is the main routine for this class.
40
41        The actual query will not be executed until such a time as the return value (or some derivative thereof)
42        is awaited.
43
44        :returns: an Result object
45        """
46        ...

A class which encapsulates a Transaction. Should never be created directly, but always via a Session object.

Should always be used as an Async Context Manager. Using any of its methods outside of the context may lead to undefined behaviour.

@abstractmethod
def run(self, *args, **kwargs) -> Result:
37    @abstractmethod
38    def run(self, *args, **kwargs) -> Result:
39        """This is the main routine for this class.
40
41        The actual query will not be executed until such a time as the return value (or some derivative thereof)
42        is awaited.
43
44        :returns: an Result object
45        """
46        ...

This is the main routine for this class.

The actual query will not be executed until such a time as the return value (or some derivative thereof) is awaited.

:returns: an Result object

class Result(typing.Generic[~R], typing.Awaitable[~R]):
30class Result (Generic[R], Awaitable[R], metaclass=ABCMeta):
31    """A conceptual wrapper for a query which will return a result object of some kind.
32
33    To execute the query and return the underlying object await this object.
34
35    It also has other methods which return awaitable objects.
36    """
37
38    @abstractmethod
39    def single(self) -> Record:
40        """Returns a Record object
41
42        Useful if the wrapped query is expected to return a single result.
43        """
44        ...
45
46    @abstractmethod
47    async def data(self) -> List[Dict[str, Any]]:
48        """Deserialises the data in this result as a list of dictionaries and returns it.
49        """
50        ...
51
52    @abstractmethod
53    def graph(self) -> Graph:
54        """Returns an Graph object which
55
56        Useful if the wrapped query is expected to return multiple results.
57        """
58        ...

A conceptual wrapper for a query which will return a result object of some kind.

To execute the query and return the underlying object await this object.

It also has other methods which return awaitable objects.

@abstractmethod
def single(self) -> aiocypher.interface.record.Record:
38    @abstractmethod
39    def single(self) -> Record:
40        """Returns a Record object
41
42        Useful if the wrapped query is expected to return a single result.
43        """
44        ...

Returns a Record object

Useful if the wrapped query is expected to return a single result.

@abstractmethod
async def data(self) -> List[Dict[str, Any]]:
46    @abstractmethod
47    async def data(self) -> List[Dict[str, Any]]:
48        """Deserialises the data in this result as a list of dictionaries and returns it.
49        """
50        ...

Deserialises the data in this result as a list of dictionaries and returns it.

@abstractmethod
def graph(self) -> aiocypher.interface.graph.Graph:
52    @abstractmethod
53    def graph(self) -> Graph:
54        """Returns an Graph object which
55
56        Useful if the wrapped query is expected to return multiple results.
57        """
58        ...

Returns an Graph object which

Useful if the wrapped query is expected to return multiple results.

class Relationship(typing.Mapping[str, typing.Union[str, int]]):
26class Relationship (Mapping[str, Union[str, int]], metaclass=ABCMeta):
27    type: str
28    start_node: Node
29    end_node: Node

A Mapping is a generic container for associating key/value pairs.

This class provides concrete generic implementations of all methods except for __getitem__, __iter__, and __len__.

type: str
start_node: Node
end_node: Node
Inherited Members
collections.abc.Mapping
get
keys
items
values
class Node(typing.Mapping[str, typing.Union[str, int, typing.List[typing.Union[str, int]]]]):
24class Node (Mapping[str, Union[str, int, List[Union[str, int]]]], metaclass=ABCMeta):
25    labels: Set[str]

A Mapping is a generic container for associating key/value pairs.

This class provides concrete generic implementations of all methods except for __getitem__, __iter__, and __len__.

labels: Set[str]
Inherited Members
collections.abc.Mapping
get
keys
items
values
class EmptyResult(aiocypher.Result[None]):
43class EmptyResult(Result[None]):
44    def single(self) -> Record:
45        """Returns a Record object
46
47        Useful if the wrapped query is expected to return a single result.
48        """
49        raise RuntimeError("This result is empty")
50
51    async def data(self) -> List[Dict[str, Any]]:
52        """Deserialises the data in this result as a list of dictionaries and returns it.
53        """
54        return []
55
56    def graph(self) -> EmptyGraph:
57        """Returns an Graph object which
58
59        Useful if the wrapped query is expected to return multiple results.
60        """
61        return EmptyGraph()
62
63    def __await__(self):
64        async def __inner():
65            return None
66        return __inner().__await__()

A conceptual wrapper for a query which will return a result object of some kind.

To execute the query and return the underlying object await this object.

It also has other methods which return awaitable objects.

def single(self) -> aiocypher.interface.record.Record:
44    def single(self) -> Record:
45        """Returns a Record object
46
47        Useful if the wrapped query is expected to return a single result.
48        """
49        raise RuntimeError("This result is empty")

Returns a Record object

Useful if the wrapped query is expected to return a single result.

async def data(self) -> List[Dict[str, Any]]:
51    async def data(self) -> List[Dict[str, Any]]:
52        """Deserialises the data in this result as a list of dictionaries and returns it.
53        """
54        return []

Deserialises the data in this result as a list of dictionaries and returns it.

def graph(self) -> aiocypher.empty.EmptyGraph:
56    def graph(self) -> EmptyGraph:
57        """Returns an Graph object which
58
59        Useful if the wrapped query is expected to return multiple results.
60        """
61        return EmptyGraph()

Returns an Graph object which

Useful if the wrapped query is expected to return multiple results.

class QueryFailed(builtins.Exception):
22class QueryFailed (Exception):
23    def __init__(self, query: str, params: Dict[str, Union[str, int]]):
24        super().__init__(f"Query failed: << {query} >> with parameters {params!r}")

Common base class for all non-exit exceptions.

QueryFailed(query: str, params: Dict[str, Union[str, int]])
23    def __init__(self, query: str, params: Dict[str, Union[str, int]]):
24        super().__init__(f"Query failed: << {query} >> with parameters {params!r}")
Inherited Members
builtins.BaseException
with_traceback
add_note
args
class Config(typing.NamedTuple):
22class Config(NamedTuple):
23    address: str
24    username: str
25    password: str
26
27    @property
28    def auth(self) -> Tuple[str, str]:
29        return (self.username, self.password)

Config(address, username, password)

Config(address: str, username: str, password: str)

Create new instance of Config(address, username, password)

address: str

Alias for field number 0

username: str

Alias for field number 1

password: str

Alias for field number 2

auth: Tuple[str, str]
Inherited Members
builtins.tuple
index
count