From e8c22b00c391282c2fb59b4a567275b89d45dd6f Mon Sep 17 00:00:00 2001 From: Suren Khorenyan Date: Thu, 4 Oct 2018 20:11:46 +0300 Subject: [PATCH 01/18] Create safe_kwargs util --- aioalice/utils/__init__.py | 1 + aioalice/utils/safe_kwargs.py | 21 +++++++++++++++++++++ 2 files changed, 22 insertions(+) create mode 100644 aioalice/utils/safe_kwargs.py diff --git a/aioalice/utils/__init__.py b/aioalice/utils/__init__.py index 9a51ecb..3e612d0 100644 --- a/aioalice/utils/__init__.py +++ b/aioalice/utils/__init__.py @@ -1,6 +1,7 @@ from . import exceptions from .json import json from .payload import generate_json_payload +from .safe_kwargs import safe_kwargs def ensure_cls(klass): diff --git a/aioalice/utils/safe_kwargs.py b/aioalice/utils/safe_kwargs.py new file mode 100644 index 0000000..76b2523 --- /dev/null +++ b/aioalice/utils/safe_kwargs.py @@ -0,0 +1,21 @@ +# https://gist.github.com/surik00/a6c2804a2d18a2ab75630bb5d93693c8 + +import inspect +import functools + + +def check_spec(func: callable, kwargs: dict): + spec = inspect.getfullargspec(func) + if spec.varkw: + return kwargs + + return {k: v for k, v in kwargs.items() if k in spec.args} + + +def safe_kwargs(func_or_class): + @functools.wraps(func_or_class) + def wrap(*args, **kwargs): + spec = check_spec(func_or_class, kwargs) + return func_or_class(*args, **spec) + + return wrap From 8a303371c2bb4d08381d6746da2b44f67f2042df Mon Sep 17 00:00:00 2001 From: Suren Khorenyan Date: Thu, 4 Oct 2018 22:21:02 +0300 Subject: [PATCH 02/18] Optimize safe_kwargs --- aioalice/utils/safe_kwargs.py | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) diff --git a/aioalice/utils/safe_kwargs.py b/aioalice/utils/safe_kwargs.py index 76b2523..42d46c4 100644 --- a/aioalice/utils/safe_kwargs.py +++ b/aioalice/utils/safe_kwargs.py @@ -4,18 +4,13 @@ import functools -def check_spec(func: callable, kwargs: dict): - spec = inspect.getfullargspec(func) - if spec.varkw: - return kwargs - - return {k: v for k, v in kwargs.items() if k in spec.args} - - def safe_kwargs(func_or_class): + spec = inspect.getfullargspec(func_or_class) + all_args = spec.args + @functools.wraps(func_or_class) def wrap(*args, **kwargs): - spec = check_spec(func_or_class, kwargs) - return func_or_class(*args, **spec) + accepted_kwargs = {k: v for k, v in kwargs.items() if k in all_args} + return func_or_class(*args, **accepted_kwargs) return wrap From bfb4a6e71c2f5d2a63114c4089604aef8b3270e9 Mon Sep 17 00:00:00 2001 From: Suren Khorenyan Date: Thu, 4 Oct 2018 22:23:30 +0300 Subject: [PATCH 03/18] Upgrade Meta and Request with safe_kwargs --- aioalice/types/meta.py | 2 ++ aioalice/types/request.py | 2 ++ 2 files changed, 4 insertions(+) diff --git a/aioalice/types/meta.py b/aioalice/types/meta.py index b7f02db..19d9ab7 100644 --- a/aioalice/types/meta.py +++ b/aioalice/types/meta.py @@ -1,7 +1,9 @@ from attr import attrs, attrib +from aioalice.utils import safe_kwargs from . import AliceObject +@safe_kwargs @attrs class Meta(AliceObject): """Meta object""" diff --git a/aioalice/types/request.py b/aioalice/types/request.py index a332839..6084506 100644 --- a/aioalice/types/request.py +++ b/aioalice/types/request.py @@ -1,9 +1,11 @@ from attr import attrs, attrib +from aioalice.utils import safe_kwargs from aioalice.utils.helper import Helper, HelperMode, Item from . import AliceObject, Markup +@safe_kwargs @attrs class Request(AliceObject): """Request object""" From 9abacb3a34a4dc7ac820cac7c62d96681aed43c7 Mon Sep 17 00:00:00 2001 From: Suren Khorenyan Date: Thu, 4 Oct 2018 22:35:40 +0300 Subject: [PATCH 04/18] Refactoring: import utils before local relative import --- aioalice/types/alice_request.py | 2 +- aioalice/types/alice_response.py | 2 +- aioalice/types/card.py | 2 +- aioalice/types/card_footer.py | 2 +- aioalice/types/image.py | 2 +- aioalice/types/response.py | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-) diff --git a/aioalice/types/alice_request.py b/aioalice/types/alice_request.py index bbcbcdd..7811e67 100644 --- a/aioalice/types/alice_request.py +++ b/aioalice/types/alice_request.py @@ -1,7 +1,7 @@ from attr import attrs, attrib +from aioalice.utils import ensure_cls from . import AliceObject, Meta, Session, \ Card, Request, Response, AliceResponse -from aioalice.utils import ensure_cls @attrs diff --git a/aioalice/types/alice_response.py b/aioalice/types/alice_response.py index 5cfb2cf..e4e28aa 100644 --- a/aioalice/types/alice_response.py +++ b/aioalice/types/alice_response.py @@ -1,6 +1,6 @@ from attr import attrs, attrib -from . import AliceObject, BaseSession, Response from aioalice.utils import ensure_cls +from . import AliceObject, BaseSession, Response @attrs diff --git a/aioalice/types/card.py b/aioalice/types/card.py index 888d041..e086699 100644 --- a/aioalice/types/card.py +++ b/aioalice/types/card.py @@ -1,8 +1,8 @@ from attr import attrs, attrib -from . import AliceObject, MediaButton, Image, CardHeader, CardFooter from aioalice.utils import ensure_cls from aioalice.utils.helper import Helper, HelperMode, Item +from . import AliceObject, MediaButton, Image, CardHeader, CardFooter @attrs diff --git a/aioalice/types/card_footer.py b/aioalice/types/card_footer.py index a0947d4..8d16066 100644 --- a/aioalice/types/card_footer.py +++ b/aioalice/types/card_footer.py @@ -1,7 +1,7 @@ from attr import attrs, attrib -from . import AliceObject, MediaButton from aioalice.utils import ensure_cls +from . import AliceObject, MediaButton @attrs diff --git a/aioalice/types/image.py b/aioalice/types/image.py index ece7daa..fa4a0da 100644 --- a/aioalice/types/image.py +++ b/aioalice/types/image.py @@ -1,7 +1,7 @@ from attr import attrs, attrib -from . import AliceObject, MediaButton from aioalice.utils import ensure_cls +from . import AliceObject, MediaButton @attrs diff --git a/aioalice/types/response.py b/aioalice/types/response.py index e3cf260..ff59ba1 100644 --- a/aioalice/types/response.py +++ b/aioalice/types/response.py @@ -1,6 +1,6 @@ from attr import attrs, attrib -from . import AliceObject, Card, Button from aioalice.utils import ensure_cls +from . import AliceObject, Card, Button @attrs From df2cd617edfbdc706fd69794190b4ccb0668bb9f Mon Sep 17 00:00:00 2001 From: Suren Khorenyan Date: Thu, 4 Oct 2018 23:20:09 +0300 Subject: [PATCH 05/18] Create EntityToken --- aioalice/types/entity_token.py | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 aioalice/types/entity_token.py diff --git a/aioalice/types/entity_token.py b/aioalice/types/entity_token.py new file mode 100644 index 0000000..50912a4 --- /dev/null +++ b/aioalice/types/entity_token.py @@ -0,0 +1,9 @@ +from attr import attrs, attrib +from . import AliceObject + + +@attrs +class EntityToken(AliceObject): + """EntityToken object""" + start = attrib(type=int) + end = attrib(type=int) From a3838c6355943a7c78c7f3cd457e31606d92796c Mon Sep 17 00:00:00 2001 From: Suren Khorenyan Date: Thu, 4 Oct 2018 23:20:21 +0300 Subject: [PATCH 06/18] Create EntityValue --- aioalice/types/entity_value.py | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) create mode 100644 aioalice/types/entity_value.py diff --git a/aioalice/types/entity_value.py b/aioalice/types/entity_value.py new file mode 100644 index 0000000..827df70 --- /dev/null +++ b/aioalice/types/entity_value.py @@ -0,0 +1,34 @@ +from attr import attrs, attrib +from . import AliceObject + + +@attrs +class EntityValue(AliceObject): + """EntityValue object""" + + # YANDEX.FIO + first_name = attrib(default=None, type=str) + patronymic_name = attrib(default=None, type=str) + last_name = attrib(default=None, type=str) + + # YANDEX.GEO + country = attrib(default=None, type=str) + city = attrib(default=None, type=str) + street = attrib(default=None, type=str) + house_number = attrib(default=None, type=str) + airport = attrib(default=None, type=str) + + # YANDEX.DATETIME + year = attrib(default=None, type=str) + year_is_relative = attrib(default=False, type=bool) + month = attrib(default=None, type=str) + month_is_relative = attrib(default=False, type=bool) + day = attrib(default=None, type=str) + day_is_relative = attrib(default=False, type=bool) + hour = attrib(default=None, type=str) + hour_is_relative = attrib(default=False, type=bool) + minute = attrib(default=None, type=str) + minute_is_relative = attrib(default=False, type=bool) + + # YANDEX.NUMBER + value = attrib(default=None) # integer / float From 8dc11e19285996fc29836befe3ce2e727c3f7591 Mon Sep 17 00:00:00 2001 From: Suren Khorenyan Date: Thu, 4 Oct 2018 23:20:44 +0300 Subject: [PATCH 07/18] Create Entity and EntityType --- aioalice/types/entity.py | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) create mode 100644 aioalice/types/entity.py diff --git a/aioalice/types/entity.py b/aioalice/types/entity.py new file mode 100644 index 0000000..28d0bee --- /dev/null +++ b/aioalice/types/entity.py @@ -0,0 +1,31 @@ +import logging +from attr import attrs, attrib + +from aioalice.utils import ensure_cls +from aioalice.utils.helper import Helper, HelperMode, Item +from . import AliceObject, EntityToken, EntityValue + +log = logging.getLogger(__name__) + + +@attrs +class Entity(AliceObject): + """Entity object""" + type = attrib(type=str) + tokens = attrib(convert=ensure_cls(EntityToken)) + value = attrib(convert=ensure_cls(EntityValue)) + + @type.validator + def check(self, attribute, value): + """Report unknown type""" + if value not in EntityType.all(): + log.error('Unknown Entity type! `%r`', value) + + +class EntityType(Helper): + mode = HelperMode.UPPER_DOT_SEPARATED + + YANDEX_GEO = Item() + YANDEX_FIO = Item() + YANDEX_NUMBER = Item() + YANDEX_DATETIME = Item() From ff0ae576eb66d86560ed1812f9780139b33f7d52 Mon Sep 17 00:00:00 2001 From: Suren Khorenyan Date: Thu, 4 Oct 2018 23:20:59 +0300 Subject: [PATCH 08/18] Create NaturalLanguageUnderstanding (NLU) --- aioalice/types/natural_language_understanding.py | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 aioalice/types/natural_language_understanding.py diff --git a/aioalice/types/natural_language_understanding.py b/aioalice/types/natural_language_understanding.py new file mode 100644 index 0000000..274d972 --- /dev/null +++ b/aioalice/types/natural_language_understanding.py @@ -0,0 +1,11 @@ +# Natural Language Understanding: https://medium.com/@lola.com/nlp-vs-nlu-whats-the-difference-d91c06780992 +from attr import attrs, attrib +from aioalice.utils import ensure_cls +from . import AliceObject, Entity + + +@attrs +class NaturalLanguageUnderstanding(AliceObject): + """Natural Language Understanding object""" + tokens = attrib(factory=list) + entities = attrib(factory=list, convert=ensure_cls(Entity)) From 67f562bce78343cac87522f4437e4ac2fb171a1e Mon Sep 17 00:00:00 2001 From: Suren Khorenyan Date: Thu, 4 Oct 2018 23:21:13 +0300 Subject: [PATCH 09/18] Add nlu field to Request --- aioalice/types/request.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/aioalice/types/request.py b/aioalice/types/request.py index 6084506..dd092fd 100644 --- a/aioalice/types/request.py +++ b/aioalice/types/request.py @@ -1,8 +1,8 @@ from attr import attrs, attrib -from aioalice.utils import safe_kwargs +from aioalice.utils import safe_kwargs, ensure_cls from aioalice.utils.helper import Helper, HelperMode, Item -from . import AliceObject, Markup +from . import AliceObject, Markup, NaturalLanguageUnderstanding @safe_kwargs @@ -14,6 +14,7 @@ class Request(AliceObject): original_utterance = attrib(default='', type=str) # Can be none if payload passed markup = attrib(default=None) payload = attrib(default=None) + nlu = attrib(default=None, convert=ensure_cls(NaturalLanguageUnderstanding)) @type.validator def check(self, attribute, value): From 85dea3afca4e06773afe4634922c002009c3a302 Mon Sep 17 00:00:00 2001 From: Suren Khorenyan Date: Thu, 4 Oct 2018 23:21:34 +0300 Subject: [PATCH 10/18] Add "UPPER.DOT.SEPARATED" mode for Helper --- aioalice/utils/helper.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/aioalice/utils/helper.py b/aioalice/utils/helper.py index 53a6cd3..098ace0 100644 --- a/aioalice/utils/helper.py +++ b/aioalice/utils/helper.py @@ -46,6 +46,7 @@ class HelperMode(Helper): CamelCase = 'CamelCase' snake_case = 'snake_case' lowercase = 'lowercase' + UPPER_DOT_SEPARATED = 'UPPER.DOT.SEPARATED' @classmethod def all(cls): @@ -120,16 +121,18 @@ def apply(cls, text, mode): :param mode: :return: """ - if mode == cls.SCREAMING_SNAKE_CASE: + if mode == cls.UPPER_DOT_SEPARATED: + return cls._screaming_snake_case(text).replace('_', '.') + elif mode == cls.SCREAMING_SNAKE_CASE: return cls._screaming_snake_case(text) elif mode == cls.snake_case: return cls._snake_case(text) - elif mode == cls.lowercase: - return cls._snake_case(text).replace('_', '') elif mode == cls.lowerCamelCase: return cls._camel_case(text) elif mode == cls.CamelCase: return cls._camel_case(text, True) + elif mode == cls.lowercase: + return cls._snake_case(text).replace('_', '') elif callable(mode): return mode(text) return text From fd7fd1b2c7c7e1fe016f0ad46b4af47d8f2bcbca Mon Sep 17 00:00:00 2001 From: Suren Khorenyan Date: Thu, 4 Oct 2018 23:22:04 +0300 Subject: [PATCH 11/18] Top import new models --- aioalice/types/__init__.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/aioalice/types/__init__.py b/aioalice/types/__init__.py index ca6ae89..dc9e050 100644 --- a/aioalice/types/__init__.py +++ b/aioalice/types/__init__.py @@ -1,6 +1,10 @@ from .base import AliceObject from .meta import Meta from .markup import Markup +from .entity_token import EntityToken +from .entity_value import EntityValue +from .entity import Entity, EntityType +from .natural_language_understanding import NaturalLanguageUnderstanding from .request import Request, RequestType from .session import BaseSession, Session From 34bde517d513fbe3231c2cdd62592899e9c52a64 Mon Sep 17 00:00:00 2001 From: Suren Khorenyan Date: Thu, 4 Oct 2018 23:30:41 +0300 Subject: [PATCH 12/18] Rename EntityToken -> EntityTokens --- aioalice/types/__init__.py | 2 +- aioalice/types/{entity_token.py => entity_tokens.py} | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) rename aioalice/types/{entity_token.py => entity_tokens.py} (65%) diff --git a/aioalice/types/__init__.py b/aioalice/types/__init__.py index dc9e050..86bc52a 100644 --- a/aioalice/types/__init__.py +++ b/aioalice/types/__init__.py @@ -1,7 +1,7 @@ from .base import AliceObject from .meta import Meta from .markup import Markup -from .entity_token import EntityToken +from .entity_tokens import EntityTokens from .entity_value import EntityValue from .entity import Entity, EntityType from .natural_language_understanding import NaturalLanguageUnderstanding diff --git a/aioalice/types/entity_token.py b/aioalice/types/entity_tokens.py similarity index 65% rename from aioalice/types/entity_token.py rename to aioalice/types/entity_tokens.py index 50912a4..edf9184 100644 --- a/aioalice/types/entity_token.py +++ b/aioalice/types/entity_tokens.py @@ -3,7 +3,7 @@ @attrs -class EntityToken(AliceObject): - """EntityToken object""" +class EntityTokens(AliceObject): + """EntityTokens object""" start = attrib(type=int) end = attrib(type=int) From b7450550425c83cbfdbe2fcc0e214207d086ed8f Mon Sep 17 00:00:00 2001 From: Suren Khorenyan Date: Thu, 4 Oct 2018 23:41:25 +0300 Subject: [PATCH 13/18] Create tests UNFINISHED! There's an error in API, waiting for an answer t.me/yadialogschat/15516 --- tests/_dataset.py | 88 +++++++++++++++++++++++++++++++++++++++++++++ tests/test_types.py | 61 ++++++++++++++++++++++++++++++- 2 files changed, 148 insertions(+), 1 deletion(-) diff --git a/tests/_dataset.py b/tests/_dataset.py index efe9265..ef4a8c3 100644 --- a/tests/_dataset.py +++ b/tests/_dataset.py @@ -226,3 +226,91 @@ }, 'version': '1.0' } + +ENTITY_TOKEN = { + "start": 2, + "end": 6 +} + +ENTITY_VALUE = { + "house_number": "16", + "street": "льва толстого" +} + +ENTITY = { + "tokens": ENTITY_TOKEN, + "type": "YANDEX.GEO", + "value": ENTITY_VALUE +} + +NLU = { + "tokens": [ + "закажи", + "пиццу", + "на", + "льва", + "толстого", + "16", + "на", + "завтра" + ], + "entities": [ + ENTITY, + { + "tokens": { + "start": 3, + "end": 5 + }, + "type": "YANDEX.FIO", + "value": { + "first_name": "лев", + "last_name": "толстой" + } + }, + { + "tokens": { + "start": 5, + "end": 6 + }, + "type": "YANDEX.NUMBER", + "value": 16 + }, + { + "tokens": { + "start": 6, + "end": 8 + }, + "type": "YANDEX.DATETIME", + "value": { + "day": 1, + "day_is_relative": True + } + } + ] +} + +REQUEST_WITH_NLU = { + "meta": { + "locale": "ru-RU", + "timezone": "Europe/Moscow", + "client_id": "ru.yandex.searchplugin/5.80 (Samsung Galaxy; Android 4.4)" + }, + "request": { + "command": "закажи пиццу на улицу льва толстого, 16 на завтра", + "original_utterance": "закажи пиццу на улицу льва толстого, 16 на завтра", + "type": "SimpleUtterance", + "markup": { + "dangerous_context": True + }, + "payload": {}, + "nlu": NLU, + }, + "session": { + "new": True, + "message_id": 4, + "session_id": "2eac4854-fce721f3-b845abba-20d60", + "skill_id": "3ad36498-f5rd-4079-a14b-788652932056", + "user_id": "AC9WC3DF6FCE052E45A4566A48E6B7193774B84814CE49A922E163B8B29881DC" + }, + "version": "1.0" +} diff --git a/tests/test_types.py b/tests/test_types.py index 9efe745..4e3dc3e 100644 --- a/tests/test_types.py +++ b/tests/test_types.py @@ -16,7 +16,8 @@ RESPONSE_BUTTON, \ EXPECTED_ALICE_RESPONSE_BIG_IMAGE_WITH_BUTTON, \ EXPECTED_ALICE_RESPONSE_ITEMS_LIST_WITH_BUTTON, \ - DATA_FROM_STATION + DATA_FROM_STATION, REQUEST_WITH_NLU, ENTITY_TOKEN, \ + ENTITY_VALUE, ENTITY, NLU class TestAliceTypes(unittest.TestCase): @@ -43,6 +44,59 @@ def test_markup(self): markup = types.Markup(**MARKUP) self._test_markup(markup, MARKUP) + def _test_entity_tokens(self, et, dct): + self.assertEqual(et.start, dct['start']) + self.assertEqual(et.end, dct['end']) + + def test_entity_tokens(self): + et = types.EntityTokens(**ENTITY_TOKEN) + self._test_entity_tokens(et, ENTITY_TOKEN) + + def _test_entity_value(self, ev, dct): + for key in ( + 'first_name', + 'patronymic_name', + 'last_name', + 'country', + 'city', + 'street', + 'house_number', + 'airport', + 'year', + 'year_is_relative', + 'month', + 'month_is_relative', + 'day', + 'day_is_relative', + 'hour', + 'hour_is_relative', + 'minute', + 'minute_is_relative', + 'value',): + if key in dct: + print('\nKey occured', key, '\n') + self.assertEqual(getattr(ev, key), dct[key]) + + def test_entity_value(self): + ev = types.EntityValue(**ENTITY_VALUE) + self._test_entity_value(ev, ENTITY_VALUE) + + def _test_entity(self, entity, dct): + self._test_entity_tokens(entity.tokens, dct['tokens']) + self._test_entity_value(entity.value, dct['value']) + + def test_entity(self): + pass + + def _test_nlu(self, nlu, dct): + self.assertEqual(nlu.tokens, dct['tokens']) + for entity, _dct in zip(nlu.entities, dct['entities']): + self._test_entity(entity, _dct) + + def test_nlu(self): + nlu = types.NaturalLanguageUnderstanding(**NLU) + self._test_nlu(nlu, NLU) + def _test_request(self, req, dct): self.assertEqual(req.command, dct['command']) self.assertEqual(req.original_utterance, dct['original_utterance']) @@ -51,6 +105,8 @@ def _test_request(self, req, dct): self.assertEqual(req.payload, dct['payload']) if 'markup' in dct: self._test_markup(req.markup, dct['markup']) + if 'nlu' in dct: + self._test_nlu(req.nlu, dct['nlu']) def test_request(self): request = types.Request(**REQUEST) @@ -260,6 +316,9 @@ def test_card_items_list_card_method(self): ) self._assert_payload(card_items_list, EXPECTED_CARD_ITEMS_LIST_JSON) + def test_request_with_nlu(self): + self._test_alice_request_from_dct(REQUEST_WITH_NLU) + if __name__ == '__main__': unittest.main() From 05da3d1641f23561d0c1bc510e9edb8359093dad Mon Sep 17 00:00:00 2001 From: Suren Khorenyan Date: Mon, 15 Oct 2018 10:40:27 +0300 Subject: [PATCH 14/18] Remove attr `value` from EntityValue --- aioalice/types/entity_value.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/aioalice/types/entity_value.py b/aioalice/types/entity_value.py index 827df70..1509ef5 100644 --- a/aioalice/types/entity_value.py +++ b/aioalice/types/entity_value.py @@ -29,6 +29,3 @@ class EntityValue(AliceObject): hour_is_relative = attrib(default=False, type=bool) minute = attrib(default=None, type=str) minute_is_relative = attrib(default=False, type=bool) - - # YANDEX.NUMBER - value = attrib(default=None) # integer / float From ba4d5b4e0c92f9779723ef1d20e71ec35eb33b8f Mon Sep 17 00:00:00 2001 From: Suren Khorenyan Date: Mon, 15 Oct 2018 10:45:49 +0300 Subject: [PATCH 15/18] Convert to EntityValue if not NUMBER https://t.me/yadialogschat/15864 --- aioalice/types/entity.py | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/aioalice/types/entity.py b/aioalice/types/entity.py index 28d0bee..925d847 100644 --- a/aioalice/types/entity.py +++ b/aioalice/types/entity.py @@ -3,7 +3,7 @@ from aioalice.utils import ensure_cls from aioalice.utils.helper import Helper, HelperMode, Item -from . import AliceObject, EntityToken, EntityValue +from . import AliceObject, EntityTokens, EntityValue log = logging.getLogger(__name__) @@ -12,8 +12,8 @@ class Entity(AliceObject): """Entity object""" type = attrib(type=str) - tokens = attrib(convert=ensure_cls(EntityToken)) - value = attrib(convert=ensure_cls(EntityValue)) + tokens = attrib(convert=ensure_cls(EntityTokens)) + value = attrib(factory=dict) @type.validator def check(self, attribute, value): @@ -21,6 +21,11 @@ def check(self, attribute, value): if value not in EntityType.all(): log.error('Unknown Entity type! `%r`', value) + def __attrs_post_init__(self): + """If entity type not number, convert to EntityValue""" + if self.value and self.type != EntityType.YANDEX_NUMBER: + self.value = EntityValue(**self.value) + class EntityType(Helper): mode = HelperMode.UPPER_DOT_SEPARATED From 6664afd279b0b456620e6951b2783932e2bb7153 Mon Sep 17 00:00:00 2001 From: Suren Khorenyan Date: Mon, 15 Oct 2018 10:47:42 +0300 Subject: [PATCH 16/18] Add integer entity to dataset for tests --- tests/_dataset.py | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/tests/_dataset.py b/tests/_dataset.py index ef4a8c3..14eb49c 100644 --- a/tests/_dataset.py +++ b/tests/_dataset.py @@ -243,6 +243,15 @@ "value": ENTITY_VALUE } +ENTITY_INTEGER = { + "tokens": { + "start": 5, + "end": 6 + }, + "type": "YANDEX.NUMBER", + "value": 16 +} + NLU = { "tokens": [ "закажи", @@ -267,14 +276,7 @@ "last_name": "толстой" } }, - { - "tokens": { - "start": 5, - "end": 6 - }, - "type": "YANDEX.NUMBER", - "value": 16 - }, + ENTITY_INTEGER, { "tokens": { "start": 6, From c08c1e2e7703f3f8cf52abcbc667b65843cf9098 Mon Sep 17 00:00:00 2001 From: Suren Khorenyan Date: Mon, 15 Oct 2018 10:49:32 +0300 Subject: [PATCH 17/18] Create tests for entities --- tests/test_types.py | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/tests/test_types.py b/tests/test_types.py index 4e3dc3e..34c806e 100644 --- a/tests/test_types.py +++ b/tests/test_types.py @@ -17,7 +17,7 @@ EXPECTED_ALICE_RESPONSE_BIG_IMAGE_WITH_BUTTON, \ EXPECTED_ALICE_RESPONSE_ITEMS_LIST_WITH_BUTTON, \ DATA_FROM_STATION, REQUEST_WITH_NLU, ENTITY_TOKEN, \ - ENTITY_VALUE, ENTITY, NLU + ENTITY_VALUE, ENTITY, ENTITY_INTEGER, NLU class TestAliceTypes(unittest.TestCase): @@ -72,9 +72,8 @@ def _test_entity_value(self, ev, dct): 'hour_is_relative', 'minute', 'minute_is_relative', - 'value',): + ): if key in dct: - print('\nKey occured', key, '\n') self.assertEqual(getattr(ev, key), dct[key]) def test_entity_value(self): @@ -83,10 +82,16 @@ def test_entity_value(self): def _test_entity(self, entity, dct): self._test_entity_tokens(entity.tokens, dct['tokens']) - self._test_entity_value(entity.value, dct['value']) + if entity.type == types.EntityType.YANDEX_NUMBER: + entity.value == dct['value'] + else: + self._test_entity_value(entity.value, dct['value']) def test_entity(self): - pass + entity = types.Entity(**ENTITY) + self._test_entity(entity, ENTITY) + entity_int = types.Entity(**ENTITY_INTEGER) + self._test_entity(entity_int, ENTITY_INTEGER) def _test_nlu(self, nlu, dct): self.assertEqual(nlu.tokens, dct['tokens']) From 91e742509dc014bbc92186763f2ed89227c693f6 Mon Sep 17 00:00:00 2001 From: Suren Khorenyan Date: Mon, 15 Oct 2018 10:49:54 +0300 Subject: [PATCH 18/18] Upgrade version to 1.2.0 --- aioalice/__init__.py | 2 +- setup.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/aioalice/__init__.py b/aioalice/__init__.py index 6551fc3..5e74c07 100644 --- a/aioalice/__init__.py +++ b/aioalice/__init__.py @@ -11,4 +11,4 @@ asyncio.set_event_loop_policy(uvloop.EventLoopPolicy()) -__version__ = '1.1.7' +__version__ = '1.2.0' diff --git a/setup.py b/setup.py index e1993a5..76c6876 100644 --- a/setup.py +++ b/setup.py @@ -16,7 +16,7 @@ if sys.version_info < MINIMAL_PY_VERSION: raise RuntimeError('aioAlice works only with Python {}+'.format('.'.join(map(str, MINIMAL_PY_VERSION)))) -__version__ = '1.1.7' +__version__ = '1.2.0' def get_description():