Skip to content

Commit 3ba6fba

Browse files
committed
Merge branch 'release/1.2.0.4' into main
2 parents 3adf266 + 03ea59d commit 3ba6fba

File tree

7 files changed

+70
-15
lines changed

7 files changed

+70
-15
lines changed

CHANGELOG.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,17 @@
11
# Pyttman Changelog
22

3+
4+
# V 1.2.0.4
5+
This is a hotfix release, fixing an issue with BoolEntityField instances
6+
not parsing the values correctly if the sought word is also part of the
7+
'lead' and/or 'trail' tuples.
8+
9+
10+
### **🐛 Splatted bugs and corrected issues**
11+
* **Fixes [#69](https://github.com/dotchetter/Pyttman/issues/68)**
12+
13+
14+
315
# V 1.2.0.3
416
This is a hotfix release, fixing an issue with default values in
517
TextEntityFields, causing a crash if it was combined with `as_list=True`, and

pyttman/core/entity_parsing/entity.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,3 +27,10 @@ def __eq__(self, other):
2727
and other.index_in_message == self.index_in_message
2828
except AttributeError:
2929
return False
30+
31+
def is_boolean(self) -> bool:
32+
"""
33+
Returns whether the value held in `self.value` is a bool,
34+
in which case it cannot be evaluated with string methods.
35+
"""
36+
return isinstance(self.value, bool)

pyttman/core/entity_parsing/fields.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,10 @@
22
from abc import ABC
33
from typing import Any, Sequence, Type
44

5+
from pyttman.core.entity_parsing.entity import Entity
6+
7+
from pyttman.core.containers import MessageMixin, Message
8+
59
from pyttman.core.entity_parsing.identifiers import IntegerIdentifier, \
610
Identifier
711
from pyttman.core.entity_parsing.parsers import EntityFieldValueParser
@@ -155,6 +159,15 @@ def __init__(self, *args,
155159
**kwargs):
156160
super().__init__(*args, valid_strings=message_contains, **kwargs)
157161

162+
def parse_message(self,
163+
message: MessageMixin,
164+
original_message_content: tuple[str],
165+
memoization: dict = None) -> None:
166+
original_content = Message(original_message_content).lowered_content()
167+
self.value = Entity(value=self.default, is_fallback_default=True)
168+
if set(self.valid_strings).intersection(original_content):
169+
self.value = Entity(value=True)
170+
158171

159172
if __name__ != "__main__":
160173
StringEntityField = TextEntityField

pyttman/core/entity_parsing/parsers.py

Lines changed: 27 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -99,13 +99,23 @@ def reset(self) -> None:
9999
"""
100100
self.value = None
101101

102-
def parse_message(self, message: MessageMixin,
102+
def parse_message(self,
103+
message: MessageMixin,
104+
original_message_content: tuple[str],
103105
memoization: dict = None) -> None:
104106
"""
105107
Walk the message and parse it for values.
106108
If the identified value exists in memoization,
107109
traverse on until value is returned or the
108110
message is exhausted.
111+
112+
:param message: A Message object to pass to each EntityField,
113+
for parsing entities.
114+
:param original_message_content: The original untouched contents of
115+
the message as received from the client. This is used for a source
116+
of truth, since the content in `message` is mutated with each entity
117+
field parsing it.
118+
:param memoization: Dictionary with previously identified entities
109119
"""
110120
self._prepare_params()
111121
if self.valid_strings:
@@ -305,7 +315,7 @@ def _identify_value(self, message: MessageMixin,
305315
# sure it complies with Pre- and/or suffix values, if configured
306316
if self.identifier is not None:
307317
identifier_object = self.identifier(start_index=start_index)
308-
identifier_entity = identifier_object .try_identify_entity(message)
318+
identifier_entity = identifier_object.try_identify_entity(message)
309319

310320
if identifier_entity is not None:
311321
allowed_scenarios = {
@@ -393,17 +403,22 @@ def _identify_value(self, message: MessageMixin,
393403

394404

395405
def parse_entities(message: MessageMixin,
396-
entity_fields: dict,
397-
exclude: tuple = None) -> dict:
406+
entity_fields: dict[str, EntityFieldValueParser],
407+
original_message_content: tuple[str],
408+
exclude: tuple = None) -> dict[str, str]:
398409
"""
399410
Traverse over all fields which are EntityFieldValueParser subclasses.
400411
Have them identify their values according to their
401412
constraints and conditions, and store them in a
402413
dictionary, returned at the end of parsing.
403-
:param exclude:
404-
:param entity_fields:
414+
:param original_message_content: The original untouched contents of
415+
the message as received from the client. This is used for a source
416+
of truth, since the content in `message` is mutated with each entity
417+
field parsing it.
418+
:param exclude: Optional tuple of strings to ignore in parsing.
419+
:param entity_fields: Dictionary with `name: EntityField` mapped
405420
:param message: MessageMixin subclass object to be parsed.
406-
:return:
421+
:return: Dictionary with the name of the entity against its parsed value.
407422
"""
408423
output = {}
409424
if exclude is None:
@@ -426,7 +441,8 @@ def parse_entities(message: MessageMixin,
426441
entity_field_instance.exclude = exclude
427442
entity_field_instance.parse_message(
428443
message,
429-
memoization=parsers_memoization)
444+
memoization=parsers_memoization,
445+
original_message_content=original_message_content)
430446

431447
# See what the parser found - Entity or None.
432448
# Ignore entities in self.exclude.
@@ -458,6 +474,9 @@ def parse_entities(message: MessageMixin,
458474
parser_joined_suffixes_and_prefixes)
459475

460476
for field_name, entity in reversed(output.items()):
477+
if entity.is_boolean():
478+
continue
479+
461480
entity_field = entity_fields.get(field_name)
462481
duplicate_cache.update(entity_field.case_preserved_cache)
463482
value_for_type_conversion = entity_field.default

pyttman/core/middleware/routing.py

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import abc
22
import random
33
import warnings
4+
from copy import copy
45
from typing import List, Any
56

67
import pyttman
@@ -95,9 +96,10 @@ def process(message: Message,
9596
if i.casefold() not in joined_patterns]
9697
truncated_message = Message(content=truncated_content)
9798
entities: dict[str: Any] = parse_entities(
98-
truncated_message,
99-
intent.user_entity_fields,
100-
intent.ignore_in_entities)
99+
message=truncated_message,
100+
entity_fields=intent.user_entity_fields,
101+
original_message_content=copy(message.content),
102+
exclude=intent.ignore_in_entities)
101103

102104
message.entities = {k: v.value for k, v in entities.items()}
103105

pyttman/version.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,2 @@
11

2-
__version__ = "1.2.0.3"
2+
__version__ = "1.2.0.4"

tests/core/entity_parsing/test_entity_fields/test_entity_parsing.py

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -205,21 +205,22 @@ class PyttmanIntentInternalEntityParserTestWebscraperApp(
205205
PyttmanInternalTestBaseCase
206206
):
207207
process_message = True
208-
mock_message = Message("Search for ManufacturerA ManufacturerB Model123 "
208+
mock_message = Message("monitor ManufacturerA ManufacturerB Model123 "
209209
"on page_a and page_b price 45000 60 results")
210210
expected_entities = {
211211
"manufacturer": "ManufacturerA ManufacturerB",
212212
"model": "Model123",
213213
"pages": ["page_a", "page_b"],
214214
"minimum_price": 45000,
215+
"create_monitor": True,
215216
"maximum_results": 60}
216217

217218
class IntentClass(ImplementedTestIntent):
218219
"""
219220
This test checks The TextEntityField, and asserts that the 'default'
220221
argument works as expected.
221222
"""
222-
lead = ("Search",)
223+
lead = ("monitor", "search")
223224
ignore_in_entities = ("search", "for", "on")
224225
manufacturer = TextEntityField(span=2)
225226
model = TextEntityField(prefixes=(manufacturer,))
@@ -230,6 +231,7 @@ class IntentClass(ImplementedTestIntent):
230231
prefixes=("price",))
231232
maximum_results = IntegerEntityField(suffixes=("results",),
232233
identifier=NumberIdentifier)
234+
create_monitor = BoolEntityField(message_contains=("monitor", "monitoring"))
233235

234236

235237
def get_valid_strings() -> tuple:
@@ -331,7 +333,7 @@ class PyttmanIntentInternalTestTrailAndLeadAreIgnored(
331333
mock_message = Message("Start workshift")
332334
process_message = True
333335
expected_entities = {
334-
"is_workshift": False,
336+
"is_workshift": True,
335337
"is_break": False
336338
}
337339

0 commit comments

Comments
 (0)