Skip to content
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

6.2.2 fix logic string #184

Merged
merged 1 commit into from
Nov 9, 2023
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
2 changes: 2 additions & 0 deletions entity/constants.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@

LOGIC_STRING_OPERATORS = {'&', '|', '!'}
17 changes: 13 additions & 4 deletions entity/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
from python3_utils import compare_on_attr
from functools import reduce

from entity.constants import LOGIC_STRING_OPERATORS
from entity.exceptions import InvalidLogicStringException


Expand Down Expand Up @@ -454,18 +455,25 @@ def _process_kmatch(self, kmatch, full_set):
Every item is 2 elements - the operator and the value or list of values
"""
entity_ids = set()
operators = {'&', '|', '!'}

if isinstance(kmatch, set):
return kmatch

if len(kmatch) == 2 and kmatch[0] not in operators:
# We can always assume operator + list where the list is either sets or another operator + list
if len(kmatch) != 2 or kmatch[0] not in LOGIC_STRING_OPERATORS:
return kmatch

# Apply the operator to the rest of the sets
if kmatch[0] == '&':
entity_ids = self._process_kmatch(kmatch[1][0], full_set) & self._process_kmatch(kmatch[1][1], full_set)
# Add the first element to the set
entity_ids.update(self._process_kmatch(kmatch[1][0], full_set))
for next_element in kmatch[1][1:]:
entity_ids &= self._process_kmatch(next_element, full_set)
elif kmatch[0] == '|':
entity_ids = self._process_kmatch(kmatch[1][0], full_set) | self._process_kmatch(kmatch[1][1], full_set)
# Add the first element to the set
entity_ids.update(self._process_kmatch(kmatch[1][0], full_set))
for next_element in kmatch[1][1:]:
entity_ids |= self._process_kmatch(next_element, full_set)
elif kmatch[0] == '!':
entity_ids = full_set - self._process_kmatch(kmatch[1], full_set)

Expand Down Expand Up @@ -526,6 +534,7 @@ def get_all_entities(self, membership_cache=None, entities_by_kind=None, return_
def get_entity_ids_from_logic_string(self, entities_by_kind, memberships):
entity_kind_id = memberships[0][1]
full_set = set(entities_by_kind[entity_kind_id]['all'])

try:
filter_tree = ast.parse(self.logic_string.lower())
except:
Expand Down
77 changes: 55 additions & 22 deletions entity/tests/model_tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -831,44 +831,77 @@ def test_logic_string(self):
def test_logic_string_not(self):
"""
Verifies that the universal set is properly fetched and used to NOT a set
Group A: 0, 1, 2
NOT(A) = 3, 4, 5, 6, 7, 8
Location A: 0, 1, 2, 3, 4, 5, 6, 7, 8, 9
Role A: 0, 1
Role B: 2, 3
Role C: 4, 5
Role D: 6, 7

Memberships:
1. User in Group A
1. Accounts in Location A
2. Accounts in Role A
3. Accounts in Role B
4. Accounts in Role C
5. Accounts in Role D

Logic: 1 AND NOT(2 OR 3 OR 4 OR 5)
Breakdown:
(0, 1, 2, 3, 4, 5, 6, 7, 8, 9) AND NOT((0, 1) OR (2, 3) OR (4, 5) OR (6, 7))
(0, 1, 2, 3, 4, 5, 6, 7, 8, 9) AND NOT(0, 1, 2, 3, 4, 5, 6, 7)
(0, 1, 2, 3, 4, 5, 6, 7, 8, 9) AND (8, 9)
(8, 9)
"""
logic_string = '1 AND NOT(2 OR 3 OR 4 OR 5)'

# Create entity kinds
location_kind = G(EntityKind)
role_kind = G(EntityKind)
account_kind = G(EntityKind)

location_a = G(Entity, entity_kind=location_kind)
role_a = G(Entity, entity_kind=role_kind)
role_b = G(Entity, entity_kind=role_kind)
role_c = G(Entity, entity_kind=role_kind)
role_d = G(Entity, entity_kind=role_kind)

Logic: NOT(1)
(3, 4, 5, 6, 7, 8)
"""
super_entity_kind = G(EntityKind)
sub_entity_kind = G(EntityKind)
super_entity_a = G(Entity, entity_kind=super_entity_kind)
sub_entities = [
G(Entity, entity_kind=sub_entity_kind)
G(Entity, entity_kind=account_kind)
for _ in range(10)
]

# Create the relationships
# Create the role relationships
relationships = [
EntityRelationship(sub_entity=sub_entities[0], super_entity=super_entity_a),
EntityRelationship(sub_entity=sub_entities[1], super_entity=super_entity_a),
EntityRelationship(sub_entity=sub_entities[2], super_entity=super_entity_a),
EntityRelationship(sub_entity=sub_entities[0], super_entity=role_a),
EntityRelationship(sub_entity=sub_entities[1], super_entity=role_a),
EntityRelationship(sub_entity=sub_entities[2], super_entity=role_b),
EntityRelationship(sub_entity=sub_entities[3], super_entity=role_b),
EntityRelationship(sub_entity=sub_entities[4], super_entity=role_c),
EntityRelationship(sub_entity=sub_entities[5], super_entity=role_c),
EntityRelationship(sub_entity=sub_entities[6], super_entity=role_d),
EntityRelationship(sub_entity=sub_entities[7], super_entity=role_d),
]

# Create location relationships
for sub_entity in sub_entities:
relationships.append(EntityRelationship(sub_entity=sub_entity, super_entity=location_a))

EntityRelationship.objects.bulk_create(relationships)

# Create the entity group
entity_group = G(EntityGroup, logic_string='NOT(1)')
entity_group = G(EntityGroup, logic_string=logic_string)

# Create the membership
G(EntityGroupMembership, entity_group=entity_group, sub_entity_kind=sub_entity_kind, entity=super_entity_a)
# Create the memberships
G(
EntityGroupMembership,
entity_group=entity_group, sub_entity_kind=account_kind, entity=location_a, sort_order=1
)
G(EntityGroupMembership, entity_group=entity_group, sub_entity_kind=account_kind, entity=role_a, sort_order=2)
G(EntityGroupMembership, entity_group=entity_group, sub_entity_kind=account_kind, entity=role_b, sort_order=3)
G(EntityGroupMembership, entity_group=entity_group, sub_entity_kind=account_kind, entity=role_c, sort_order=4)
G(EntityGroupMembership, entity_group=entity_group, sub_entity_kind=account_kind, entity=role_d, sort_order=5)

entity_ids = entity_group.get_all_entities()
self.assertEqual(entity_ids, set([
sub_entities[3].id,
sub_entities[4].id,
sub_entities[5].id,
sub_entities[6].id,
sub_entities[7].id,
sub_entities[8].id,
sub_entities[9].id,
]))
Expand Down
2 changes: 1 addition & 1 deletion entity/version.py
Original file line number Diff line number Diff line change
@@ -1 +1 @@
__version__ = '6.2.1'
__version__ = '6.2.2'
4 changes: 4 additions & 0 deletions release_notes.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
## Release Notes

- 6.2.2:
- Fixed entity group logic string for logic sets containing more than 2 items being operated on
- 6.2.1:
- Rebumped because publish messed up
- 6.2.0:
- Add support for boolean logic strings to apply to entity group memberships
- 6.1.1:
Expand Down