Skip to content
This repository has been archived by the owner on Oct 22, 2022. It is now read-only.

Commit

Permalink
add support for JSON output format in queries (#172)
Browse files Browse the repository at this point in the history
  • Loading branch information
nsidnev authored Mar 2, 2022
1 parent 343f5d5 commit f473454
Show file tree
Hide file tree
Showing 12 changed files with 361 additions and 58 deletions.
14 changes: 13 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,19 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

<small>[Compare with 0.2.0](https://github.com/nsidnev/edgeql-queries/compare/0.2.0...HEAD)</small>
<small>[Compare with 0.3.0](https://github.com/nsidnev/edgeql-queries/compare/0.3.0...HEAD)</small>

## [0.3.0] - 2022-03-02

<small>[Compare with 0.2.0](https://github.com/nsidnev/edgeql-queries/compare/0.2.0...0.3.0)</small>

### Added

- Add support for JSON output via `.json` property for [`Queries`][edgeql_queries.queries.Queries].

### Misc

- Bump dependencies.

## [0.2.0] - 2022-02-10

Expand Down
4 changes: 0 additions & 4 deletions docs/reference/executors.md
Original file line number Diff line number Diff line change
@@ -1,5 +1 @@
::: edgeql_queries.executors

::: edgeql_queries.executors.async_executor

::: edgeql_queries.executors.sync_executor
2 changes: 1 addition & 1 deletion edgeql_queries/executors/__init__.py
Original file line number Diff line number Diff line change
@@ -1 +1 @@
"""Definition for query executors creators."""
"""Definition for query executors."""
98 changes: 84 additions & 14 deletions edgeql_queries/executors/async_executor.py
Original file line number Diff line number Diff line change
@@ -1,15 +1,54 @@
"""Definition for creator of async executors."""

from functools import partial
from copy import copy
from types import MappingProxyType
from typing import Any, Callable, Mapping
from typing import Any, Awaitable, Callable, Mapping

from edgedb import AsyncIOExecutor
from edgedb.datatypes import datatypes

from edgeql_queries.executors.base_executor import BaseExecutor
from edgeql_queries.models import EdgeQLOperationType, Query


class AsyncExecutor(BaseExecutor):
"""Async executor for queries."""

def __init__(self, query: Query) -> None:
"""Initialize an executor for query.
Arguments:
query: query for execution.
"""
super().__init__()

self._query = query
self._executor = _OPERATION_TO_EXECUTOR[query.operation_type]

def __call__(self, conn: AsyncIOExecutor, *args: Any, **kwargs: Any) -> Awaitable:
"""Execute query.
Arguments:
conn: asynchronous EdgeDB executor.
args: positional arguments.
kwargs: keyword arguments.
Returns:
Result of query execution.
"""
return self._executor(self._query, conn, *args, **kwargs)

def as_json(self) -> "AsyncExecutor":
"""Create an executor copy that will use JSON as output format.
Returns:
Copied executor.
"""
executor = copy(self)
executor._executor = _OPERATION_TO_JSON_EXECUTOR[self._query.operation_type]
return executor


async def _execute(__edgeql_query__: Query, conn: AsyncIOExecutor) -> None:
return await conn.execute(__edgeql_query__.edgeql)

Expand Down Expand Up @@ -45,6 +84,41 @@ async def _required_single_return(
)


async def _json_set_return(
__edgeql_query__: Query,
conn: AsyncIOExecutor,
*query_args: Any,
**query_kwargs: Any,
) -> str:
return await conn.query_json(__edgeql_query__.edgeql, *query_args, **query_kwargs)


async def _json_single_return(
__edgeql_query__: Query,
conn: AsyncIOExecutor,
*query_args: Any,
**query_kwargs: Any,
) -> str:
return await conn.query_single_json(
__edgeql_query__.edgeql,
*query_args,
**query_kwargs,
)


async def _json_required_single_return(
__edgeql_query__: Query,
conn: AsyncIOExecutor,
*query_args: Any,
**query_kwargs: Any,
) -> str:
return await conn.query_required_single_json(
__edgeql_query__.edgeql,
*query_args,
**query_kwargs,
)


_OPERATION_TO_EXECUTOR: Mapping[EdgeQLOperationType, Callable] = MappingProxyType(
{
EdgeQLOperationType.required_single_return: _required_single_return,
Expand All @@ -54,15 +128,11 @@ async def _required_single_return(
},
)


def create_async_executor(query: Query) -> Callable:
"""Create async executor for query.
Arguments:
query: query for which executor should be created.
Returns:
Created async executor.
"""
executor = _OPERATION_TO_EXECUTOR[query.operation_type]
return partial(executor, query)
_OPERATION_TO_JSON_EXECUTOR: Mapping[EdgeQLOperationType, Callable] = MappingProxyType(
{
EdgeQLOperationType.required_single_return: _json_required_single_return,
EdgeQLOperationType.single_return: _json_single_return,
EdgeQLOperationType.set_return: _json_set_return,
EdgeQLOperationType.execute: _execute,
},
)
18 changes: 18 additions & 0 deletions edgeql_queries/executors/base_executor.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
"""Definition for base executor."""

import abc
from typing import TypeVar

ExecutorT = TypeVar("ExecutorT", bound="BaseExecutor")


class BaseExecutor(abc.ABC):
"""Base executor for queries."""

@abc.abstractmethod
def as_json(self: ExecutorT) -> ExecutorT: # pragma: no cover
"""Create an executor copy that will use JSON as output format.
Returns:
Copied executor.
"""
92 changes: 79 additions & 13 deletions edgeql_queries/executors/sync_executor.py
Original file line number Diff line number Diff line change
@@ -1,15 +1,54 @@
"""Definition for creator of sync executors."""

from functools import partial
from copy import copy
from types import MappingProxyType
from typing import Any, Callable, Mapping

from edgedb import Executor
from edgedb.datatypes import datatypes

from edgeql_queries.executors.base_executor import BaseExecutor
from edgeql_queries.models import EdgeQLOperationType, Query


class SyncExecutor(BaseExecutor):
"""Sync executor for queries."""

def __init__(self, query: Query) -> None:
"""Initialize an executor for query.
Arguments:
query: query for execution.
"""
super().__init__()

self._query = query
self._executor = _OPERATION_TO_EXECUTOR[query.operation_type]

def __call__(self, conn: Executor, *args: Any, **kwargs: Any) -> Any:
"""Execute query.
Arguments:
conn: synchronous EdgeDB executor.
args: positional arguments.
kwargs: keyword arguments.
Returns:
Result of query execution.
"""
return self._executor(self._query, conn, *args, **kwargs)

def as_json(self) -> "SyncExecutor":
"""Create an executor copy that will use JSON as output format.
Returns:
Copied executor.
"""
executor = copy(self)
executor._executor = _OPERATION_TO_JSON_EXECUTOR[self._query.operation_type]
return executor


def _execute(__edgeql_query__: Query, executor: Executor) -> None:
return executor.execute(__edgeql_query__.edgeql)

Expand Down Expand Up @@ -45,6 +84,37 @@ def _required_single_return(
)


def _json_set_return(
__edgeql_query__: Query,
conn: Executor,
*query_args: Any,
**query_kwargs: Any,
) -> str:
return conn.query_json(__edgeql_query__.edgeql, *query_args, **query_kwargs)


def _json_single_return(
__edgeql_query__: Query,
conn: Executor,
*query_args: Any,
**query_kwargs: Any,
) -> str:
return conn.query_single_json(__edgeql_query__.edgeql, *query_args, **query_kwargs)


def _json_required_single_return(
__edgeql_query__: Query,
conn: Executor,
*query_args: Any,
**query_kwargs: Any,
) -> str:
return conn.query_required_single_json(
__edgeql_query__.edgeql,
*query_args,
**query_kwargs,
)


_OPERATION_TO_EXECUTOR: Mapping[EdgeQLOperationType, Callable] = MappingProxyType(
{
EdgeQLOperationType.required_single_return: _required_single_return,
Expand All @@ -54,15 +124,11 @@ def _required_single_return(
},
)


def create_sync_executor(query: Query) -> Callable:
"""Create sync executor for query.
Arguments:
query: query for which executor should be created.
Returns:
Created sync executor.
"""
executor = _OPERATION_TO_EXECUTOR[query.operation_type]
return partial(executor, query)
_OPERATION_TO_JSON_EXECUTOR: Mapping[EdgeQLOperationType, Callable] = MappingProxyType(
{
EdgeQLOperationType.required_single_return: _json_required_single_return,
EdgeQLOperationType.single_return: _json_single_return,
EdgeQLOperationType.set_return: _json_set_return,
EdgeQLOperationType.execute: _execute,
},
)
12 changes: 1 addition & 11 deletions edgeql_queries/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ class EdgeQLOperationType(IntEnum):
execute = auto()


@dataclass
@dataclass(frozen=True)
class Query:
"""Parsed query."""

Expand All @@ -33,16 +33,6 @@ class Query:
#: EdgeQL query that should be executed.
edgeql: str

def __hash__(self) -> int:
"""Hash query.
Hashing is done by query's name.
Returns:
Query's hash.
"""
return hash(self.name)

def __str__(self) -> str:
"""Return string representation of query.
Expand Down
47 changes: 34 additions & 13 deletions edgeql_queries/queries.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,13 @@

from typing import Callable, Dict, List, Set, Union

from edgeql_queries.executors.async_executor import create_async_executor
from edgeql_queries.executors.sync_executor import create_sync_executor
from edgeql_queries.executors.async_executor import AsyncExecutor
from edgeql_queries.executors.sync_executor import SyncExecutor
from edgeql_queries.models import Query
from edgeql_queries.typing import QueriesTree


def _create_handler_from_query(query: Query, use_async: bool = True) -> Callable:
if use_async:
return create_async_executor(query)

return create_sync_executor(query)
Executor = Union[AsyncExecutor, SyncExecutor]
QueryHandler = Union[Executor, "Queries"]


def load_from_list(queries_collection: Queries, queries: List[Query]) -> Queries:
Expand Down Expand Up @@ -64,10 +60,11 @@ def __init__(self, is_async: bool = True) -> None:
Arguments:
is_async: use async driver for creating queries.
"""
self._query_handlers: Dict[str, Union[Callable, "Queries"]] = {}
self._query_handlers: Dict[str, QueryHandler] = {}
self._available_queries: Set[Query] = set()
self._available_queries_groups: Dict[str, Queries] = {}
self._is_async = is_async
self._json = False

@property
def available_queries(self) -> List[Query]:
Expand All @@ -87,6 +84,30 @@ def is_async(self) -> bool:
"""
return self._is_async

@property
def json(self) -> "Queries":
"""Return copy of queries that will use JSON as output format.
Returns:
Copied queries.
"""
handlers = {}
for name, query_handler in self._query_handlers.items():
if isinstance(query_handler, Queries):
query_handler = query_handler.json
else:
query_handler = query_handler.as_json()

handlers[name] = query_handler

queries = self.__class__()
queries._query_handlers = handlers
queries._json = True
queries._available_queries = self._available_queries
queries._available_queries_groups = self._available_queries_groups
queries._is_async = self._is_async
return queries

def add_query(self, name: str, query_handler: Union[Queries, Query]) -> None:
"""Add a single query to collection.
Expand All @@ -102,10 +123,10 @@ def add_query(self, name: str, query_handler: Union[Queries, Query]) -> None:
if isinstance(query_handler, Query):
self._available_queries.add(query_handler)

handler_for_query = _create_handler_from_query(
query_handler,
self._is_async,
)
if self._is_async:
handler_for_query = AsyncExecutor(query_handler)
else:
handler_for_query = SyncExecutor(query_handler)
else:
handler_for_query = query_handler
self._available_queries_groups[name] = handler_for_query
Expand Down
Loading

1 comment on commit f473454

@github-actions
Copy link

Choose a reason for hiding this comment

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

Please sign in to comment.