8000 refactor: avoid import drivers from types by JoanFM · Pull Request #1759 · jina-ai/serve · GitHub
[go: up one dir, main page]
More Web Proxy on the site http://driver.im/
Skip to content

refactor: avoid import drivers from types #1759

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 8 commits into from
Jan 26, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 2 additions & 5 deletions jina/clients/request.py
Original file line number Diff line number Diff line change
Expand Up @@ -112,11 +112,8 @@ def search(*args, **kwargs):
"""Generate a searching request """
if ('top_k' in kwargs) and (kwargs['top_k'] is not None):
# associate all VectorSearchDriver and SliceQL driver to use top_k
# TODO: not really elegant, to be refactored (Han)
from ..drivers.querylang.slice import SliceQL
from ..drivers.search import VectorSearchDriver
topk_ql = [QueryLang(SliceQL(start=0, end=kwargs['top_k'], priority=1)),
QueryLang(VectorSearchDriver(top_k=kwargs['top_k'], priority=1))]
topk_ql = [QueryLang({'name': 'SliceQL', 'priority': 1, 'parameters': {'end': kwargs['top_k']}}),
QueryLang({'name': 'VectorSearchDriver', 'priority': 1, 'parameters': {'top_k': kwargs['top_k']}})]
if 'queryset' not in kwargs:
kwargs['queryset'] = topk_ql
else:
Expand Down
6 changes: 6 additions & 0 deletions jina/drivers/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
from ..executors.decorators import wrap_func
from ..helper import convert_tuple_to_list
from ..jaml import JAMLCompatible
from ..types.querylang import QueryLang

if False:
# fix type-hint complain for sphinx and flake
Expand Down Expand Up @@ -96,6 +97,11 @@ class QuerySetReader:

"""

@property
def as_querylang(self):
parameters = {name: getattr(self, name) for name in self._init_kwargs_dict.keys()}
return QueryLang({'name': self.__class__.__name__, 'priority': self._priority, 'parameters': parameters})

def _get_parameter(self, key: str, default: Any):
if getattr(self, 'queryset', None):
for q in self.queryset:
Expand Down
8 changes: 4 additions & 4 deletions jina/drivers/querylang/select.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
__copyright__ = "Copyright (c) 2020 Jina AI Limited. All rights reserved."
__license__ = "Apache-2.0"

from typing import Tuple
from typing import Union, Tuple

from .. import QuerySetReader, BaseRecursiveDriver

Expand All @@ -21,16 +21,16 @@ class ExcludeQL(QuerySetReader, BaseRecursiveDriver):
ExcludeQL will avoid `buffer` and `chunks` fields to be sent to the next `Pod`
"""

def __init__(self, fields: Tuple, traversal_paths: Tuple[str] = ('c',), *args, **kwargs):
def __init__(self, fields: Union[Tuple, str], traversal_paths: Tuple[str] = ('c',), *args, **kwargs):
"""

:param fields: the pruned field names in tuple
"""
super().__init__(traversal_paths=traversal_paths, *args, **kwargs)
if isinstance(fields, str):
self._fields = {fields, }
self._fields = [fields]
else:
self._fields = set(fields)
self._fields = [field for field in fields]

def _apply_all(self, docs: 'DocumentSet', *args, **kwargs):
for doc in docs:
Expand Down
50 changes: 10 additions & 40 deletions jina/types/querylang/__init__.py
Original file line number Diff line number Diff line change
@@ -1,17 +1,15 @@
import warnings
from typing import TypeVar, Dict, Optional, Type
from typing import TypeVar, Dict, Optional

from google.protobuf import json_format

from ...drivers import BaseDriver
from ...excepts import BadQueryLangType
from ...helper import typename
from ...importer import import_classes
from ...proto import jina_pb2


QueryLangSourceType = TypeVar('QueryLangSourceType',
jina_pb2.QueryLangProto, bytes, str, Dict, BaseDriver)
jina_pb2.QueryLangProto, bytes, str, Dict)

__all__ = ['QueryLang']

Expand All @@ -23,40 +21,36 @@ class QueryLang:
It offers a Pythonic interface to allow users access and manipulate
:class:`jina.jina_pb2.QueryLangProto` object without working with Protobuf itself.

To create a :class:`QueryLang` object from a :class:`BaseDriver` object, simply:
To create a :class:`QueryLang` object from a Dict containing the name of a :class:`BaseDriver`,
and the parameters to override, simply:

.. highlight:: python
.. code-block:: python

from jina import QueryLang
from jina.drivers.querylang.slice import SliceQL
ql = QueryLang({name: 'SliceQL', priority: 1, parameters: {'start': 3, 'end': 1}})

s = SliceQL(start=3, end=4)
ql = QueryLang(s)
.. warning::
The `BaseDriver` needs to be a `QuerySetReader` to be able to read the `QueryLang`

One can also build a :class`QueryLang` from JSON string, bytes, dict or directly from a protobuf object.

A :class:`QueryLang` object (no matter how it is constructed) can be converted to
protobuf object or back to driver object by using:
protobuf object by using:

.. highlight:: python
.. code-block:: python

# to protobuf object
s.as_pb_object

# to driver object
s.as_driver_object

To get the class name of the associated driver, one can use :attr:`driver`.
ql.as_pb_object

"""

def __init__(self, querylang: Optional[QueryLangSourceType] = None, copy: bool = False):
"""

:param querylang: the query language source to construct from, acceptable types include:
:class:`jina_pb2.QueryLangProto`, :class:`bytes`, :class:`str`, :class:`Dict`, :class:`BaseDriver`.
:class:`jina_pb2.QueryLangProto`, :class:`bytes`, :class:`str`, :class:`Dict`, Tuple.
:param copy: when ``querylang`` is given as a :class:`QueryLangProto` object, build a
view (i.e. weak reference) from it or a deep copy from it.
"""
Expand Down Expand Up @@ -84,10 +78,6 @@ def __init__(self, querylang: Optional[QueryLangSourceType] = None, copy: bool =
self._querylang.ParseFromString(querylang)
except RuntimeWarning as ex:
raise BadQueryLangType('fail to construct a query language') from ex
elif isinstance(querylang, BaseDriver):
self.driver = querylang
self.priority = querylang._priority
self._querylang.parameters.update(querylang._init_kwargs_dict)
elif querylang is not None:
# note ``None`` is not considered as a bad type
raise ValueError(f'{typename(querylang)} is not recognizable')
Expand All @@ -114,30 +104,10 @@ def name(self, value: str):
"""Set the name of the driver that the query language attached to """
self._querylang.name = value

@property
def driver(self) -> Type['BaseDriver']:
"""Get the driver class that the query language attached to

..warning::
This browses all module trees and can be costly,
do not frequently call it.
"""
return import_classes('jina.drivers', targets=[self.name])

@driver.setter
def driver(self, value: 'BaseDriver'):
"""Set the driver class that the query language attached to """
self._querylang.name = value.__class__.__name__

def __getattr__(self, name: str):
return getattr(self._querylang, name)

@property
def as_pb_object(self) -> 'jina_pb2.QueryLangProto':
"""Return a protobuf :class:`jina_pb2.QueryLangProto` object """
return self._querylang

@property
def as_driver_object(self) -> 'BaseDriver':
"""Return a :class:`BaseDriver` object """
return self.driver(**self._querylang.parameters)
7 changes: 3 additions & 4 deletions jina/types/sets/querylang.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,13 @@
from collections.abc import MutableSequence
from typing import Iterable, Union
from typing import Iterable, Union, Dict

from google.protobuf.pyext._message import RepeatedCompositeContainer

from ..querylang import QueryLang
from ...drivers import BaseDriver
from ...helper import typename
from ...proto.jina_pb2 import QueryLangProto

AcceptQueryLangType = Union[QueryLang, BaseDriver, QueryLangProto]
AcceptQueryLangType = Union[QueryLang, QueryLangProto, Dict]

__all__ = ['QueryLangSet', 'AcceptQueryLangType']

Expand Down Expand Up @@ -55,7 +54,7 @@ def __getitem__(self, item):

def append(self, value: 'AcceptQueryLangType'):
q_pb = self._querylangs_proto.add()
if isinstance(value, BaseDriver):
if isinstance(value, Dict):
q_pb.CopyFrom(QueryLang(value).as_pb_object)
elif isinstance(value, QueryLangProto):
q_pb.CopyFrom(value)
Expand Down
3 changes: 1 addition & 2 deletions tests/integration/issues/github_1072/test_queryset.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
import pytest

from jina import QueryLang
from jina.drivers.querylang.filter import FilterQL
from jina.flow import Flow
from jina.proto import jina_pb2
from jina.types.ndarray.generic import NdArray
Expand Down Expand Up @@ -46,7 +45,7 @@ def validate_label2_docs(resp):
# keep all the docs
f.index(docs, on_done=validate_all_docs, callback_on='body')
# keep only the docs with label2
qs = QueryLang(FilterQL(priority=1, lookups={'tags__label': 'label2'}, traversal_paths=['r']))
qs = QueryLang({'name': 'FilterQL', 'priority': 1, 'parameters': {'lookups': {'tags__label': 'label2'}, 'traversal_paths': ['r']}})
f.index(docs, queryset=qs, on_done=validate_label2_docs, callback_on='body')

mock1.assert_called_once()
Expand Down
5 changes: 1 addition & 4 deletions tests/integration/issues/github_976/test_topk.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
import pytest

from jina import QueryLang
from jina.drivers.search import VectorSearchDriver
from jina.flow import Flow
from jina.proto import jina_pb2
from jina.types.ndarray.generic import NdArray
Expand Down Expand Up @@ -59,10 +58,8 @@ def validate(resp):
for doc in resp.search.docs:
assert len(doc.matches) == TOPK_OVERRIDE



# Making queryset
top_k_queryset = QueryLang(VectorSearchDriver(top_k=TOPK_OVERRIDE, priority=1))
top_k_queryset = QueryLang({'name': 'VectorSearchDriver', 'parameters': {'top_k': TOPK_OVERRIDE}, 'priority': 1})

with Flow.load_config('flow.yml') as index_flow:
index_flow.index(input_fn=random_docs(100))
Expand Down
60 changes: 58 additions & 2 deletions tests/unit/drivers/querylang/test_querylang_reader.py 10000
Original file line number Diff line number Diff line change
@@ -1,9 +1,14 @@
import pytest

from jina.clients import Client
from jina.drivers import QuerySetReader, BaseDriver
from jina.drivers.querylang.sort import SortQL
from jina.drivers.querylang.slice import SliceQL
from jina.drivers.querylang.select import ExcludeQL
from jina.flow import Flow
from jina.proto import jina_pb2
from jina.types.querylang import QueryLang
from jina.types.sets import QueryLangSet


def random_docs(num_docs):
Expand Down Expand Up @@ -36,7 +41,7 @@ def __init__(self, arg1='hello', arg2=456, *args, **kwargs):


def test_querylang_request():
qs = QueryLang(SliceQL(start=1, end=4, priority=1))
qs = QueryLang({'name': 'SliceQL', 'parameters': {'start': 1, 'end': 4}, 'priority': 1})
Client.check_input(random_docs(10), queryset=qs)


Expand All @@ -51,7 +56,7 @@ def validate2(req):
response_mock_2 = mocker.Mock(wrap=validate2)
response_mock_3 = mocker.Mock(wrap=validate1)

qs = QueryLang(SliceQL(start=1, end=4, priority=1))
qs = QueryLang({'name': 'SliceQL', 'priority': 1, 'parameters': {'start': 1, 'end': 4}})

f = Flow(callback_on='body').add(uses='- !SliceQL | {start: 0, end: 5}')

Expand All @@ -77,3 +82,54 @@ def validate2(req):
def test_querlang_driver():
qld2 = DummyDriver(arg1='world')
assert qld2.arg1 == 'world'


def test_as_querylang():
sortql = SortQL(field='field_value', reverse=False, priority=2)
sort_querylang = sortql.as_querylang
assert sort_querylang.name == 'SortQL'
assert sort_querylang.priority == 2
assert sort_querylang.parameters['field'] == 'field_value'
assert not sort_querylang.parameters['reverse']

sliceql = SliceQL(start=10, end=20)
slice_querylang = sliceql.as_querylang
assert slice_querylang.name == 'SliceQL'
assert slice_querylang.priority == 0
assert slice_querylang.parameters['start'] == 10
assert slice_querylang.parameters['end'] == 20

excludeql = ExcludeQL(fields=('field1', 'field2'))
exclude_querylang = excludeql.as_querylang
assert exclude_querylang.name == 'ExcludeQL'
assert exclude_querylang.priority == 0
assert list(exclude_querylang.parameters['fields']) == ['field1', 'field2']

excludeql2 = ExcludeQL(fields='field1')
exclude_querylang2 = excludeql2.as_querylang
assert exclude_querylang2.name == 'ExcludeQL'
assert exclude_querylang2.priority == 0
assert list(exclude_querylang2.parameters['fields']) == ['field1']


class MockExcludeQL(ExcludeQL):

def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
ql = QueryLang({'name': 'MockExcludeQL', 'parameters': {'fields': ['updated_field1', 'updated_field2']}, 'priority': 3})
self.qset = QueryLangSet([ql.as_pb_object])

@property
def queryset(self):
return self.qset


@pytest.mark.parametrize('driver_priority', [0, 4])
def test_queryset_reader_excludeql(driver_priority):
querysetreader = MockExcludeQL(fields=('local_field1', 'local_field2'), priority=driver_priority)
fields = querysetreader._get_parameter('fields', default=None)

if driver_priority == 0:
asser CEB7 t list(fields) == ['updated_field1', 'updated_field2']
else:
assert list(fields) == ['local_field1', 'local_field2']
7 changes: 4 additions & 3 deletions tests/unit/types/message/test_message.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@

from jina import Request, QueryLang, Document
from jina.clients.request import _generate
from jina.drivers.querylang.slice import SliceQL
from jina.proto import jina_pb2
from jina.proto.jina_pb2 import EnvelopeProto
from jina.types.message import Message
Expand Down Expand Up @@ -131,8 +130,8 @@ def test_lazy_request_fields():


def test_request_extend_queryset():
q1 = SliceQL(start=3, end=4)
q2 = QueryLang(SliceQL(start=3, end=4, priority=1))
q1 = {'name': 'SliceQL', 'parameters': {'start': 3, 'end': 4}}
q2 = QueryLang({'name': 'SliceQL', 'parameters': {'start': 3, 'end': 4}, 'priority': 1})
q3 = jina_pb2.QueryLangProto()
q3.name = 'SliceQL'
q3.parameters['start'] = 3
Expand All @@ -141,6 +140,7 @@ def test_request_extend_queryset():
r = Request()
r.queryset.extend([q1, q2, q3])
assert isinstance(r.queryset, Sequence)
assert len(r.queryset) == 3
for idx, q in enumerate(r.queryset):
assert q.priority == idx
assert q.parameters['start'] == 3
Expand Down Expand Up @@ -180,6 +180,7 @@ def test_empty_request_type(typ, pb_typ):
assert r._request_type == typ
assert isinstance(r.body, pb_typ)


@pytest.mark.parametrize('typ,pb_typ', [('index', jina_pb2.RequestProto.IndexRequestProto),
('search', jina_pb2.RequestProto.SearchRequestProto)])
def test_add_doc_to_type(typ, pb_typ):
Expand Down
Loading
0