Skip to content

Commit

Permalink
Lock collection field with array & empty filter (#125)
Browse files Browse the repository at this point in the history
* Lock collection field with array & empty filter

* Remove unneeded arrays
  • Loading branch information
jsangmeister committed Apr 22, 2021
1 parent 49abad2 commit 488b224
Show file tree
Hide file tree
Showing 7 changed files with 114 additions and 30 deletions.
13 changes: 13 additions & 0 deletions writer/tests/integration/write/test_occ_locking.py
Original file line number Diff line number Diff line change
Expand Up @@ -115,3 +115,16 @@ def test_locked_collectionfield_with_filter(
write_handler.write(valid_metadata)

assert e.value.key == locked_collectionfield


def test_locked_collectionfield_with_filter_without_filter(
write_handler, connection_handler, valid_metadata
):
locked_collectionfield = MagicMock()
connection_handler.collectionfield = MagicMock(return_value=locked_collectionfield)
valid_metadata["locked_fields"]["a/f"] = {"position": 42}

with pytest.raises(ModelLocked) as e:
write_handler.write(valid_metadata)

assert e.value.key == locked_collectionfield
59 changes: 59 additions & 0 deletions writer/tests/system/write/test_occ_locking.py
Original file line number Diff line number Diff line change
Expand Up @@ -293,6 +293,65 @@ def test_lock_collectionfield_with_and_filter(json_client, data):
assert_model("a/2", {}, 3)


def test_lock_collectionfield_empty_array(json_client, data):
create_and_update_model(json_client, "a/1", {"f1": 1, "f3": False}, {"f2": 2})
data["locked_fields"]["a/f1"] = []
response = json_client.post(WRITE_URL, data)
assert_response_code(response, 400)


def test_lock_collectionfield_multiple_locks_ok(json_client, data):
create_and_update_model(json_client, "a/1", {"f1": 1}, {"f2": 2})
data["locked_fields"]["a/f1"] = [
{
"position": 1,
"filter": {
"field": "f1",
"operator": "=",
"value": 1,
},
},
{
"position": 1,
"filter": {
"field": "f2",
"operator": "=",
"value": 2,
},
},
]

response = json_client.post(WRITE_URL, data)
assert_response_code(response, 201)
assert_model("a/2", {}, 3)


def test_lock_collectionfield_multiple_locks_not_ok(json_client, data):
create_and_update_model(json_client, "a/1", {"f1": 1}, {"f2": 2})
data["locked_fields"]["a/f2"] = [
{
"position": 1,
"filter": {
"field": "f1",
"operator": "=",
"value": 1,
},
},
{
"position": 2,
"filter": {
"field": "f2",
"operator": "=",
"value": 2,
},
},
]

response = json_client.post(WRITE_URL, data)
assert_error_response(response, ERROR_CODES.MODEL_LOCKED)
assert_no_model("a/2")


# Template fields


Expand Down
2 changes: 1 addition & 1 deletion writer/tests/unit/core/test_write_request.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ def test_collectionfield_lock():
wr = WriteRequest(get_dummy_request_events(), {}, 1, locked_fields)

assert isinstance(
wr.locked_collectionfields["collection/field"], CollectionFieldLockWithFilter
wr.locked_collectionfields["collection/field"][0], CollectionFieldLockWithFilter
)


Expand Down
6 changes: 3 additions & 3 deletions writer/writer/core/occ_locker.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
from dataclasses import dataclass
from typing import Dict, Protocol, Union
from typing import Dict, List, Optional, Protocol, Union

from shared.di import service_interface
from shared.util import Filter, SelfValidatingDataclass
Expand All @@ -8,10 +8,10 @@
@dataclass
class CollectionFieldLockWithFilter(SelfValidatingDataclass):
position: int
filter: Filter
filter: Optional[Filter]


CollectionFieldLock = Union[int, CollectionFieldLockWithFilter]
CollectionFieldLock = Union[int, List[CollectionFieldLockWithFilter]]


@service_interface
Expand Down
18 changes: 12 additions & 6 deletions writer/writer/core/write_request.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
from collections import defaultdict
from typing import Any, Dict, List, TypedDict, Union, cast

from dacite import from_dict
Expand Down Expand Up @@ -84,7 +85,7 @@ class RequestRestoreEvent(BaseRequestEvent):
pass


LockedFieldsJSON = Union[int, Dict[str, Any]]
LockedFieldsJSON = Union[int, Dict[str, Any], List[Dict[str, Any]]]


class WriteRequest:
Expand All @@ -105,7 +106,7 @@ def __init__(
def parse_locked_fields(self, locked_fields: Dict[str, LockedFieldsJSON]) -> None:
self.locked_fqids: Dict[str, int] = {}
self.locked_fqfields: Dict[str, int] = {}
self.locked_collectionfields: Dict[str, CollectionFieldLock] = {}
self.locked_collectionfields: Dict[str, CollectionFieldLock] = defaultdict(list)
for key, position in locked_fields.items():
self.handle_single_key(key, position)

Expand All @@ -129,11 +130,16 @@ def handle_single_key(self, key: str, cf_lock: LockedFieldsJSON) -> None:
if isinstance(cf_lock, int):
self.locked_collectionfields[key] = cf_lock
else:
if not isinstance(cf_lock, list):
cf_lock = [cf_lock]
# build self validating data class
try:
self.locked_collectionfields[key] = from_dict(
CollectionFieldLockWithFilter,
cf_lock,
)
for lock in cf_lock:
self.locked_collectionfields[key].append( # type: ignore
from_dict(
CollectionFieldLockWithFilter,
lock,
)
)
except (TypeError, MissingValueError, UnionMatchError) as e:
raise BadCodingError("Invalid data to initialize class\n" + str(e))
19 changes: 13 additions & 6 deletions writer/writer/flask_frontend/json_handlers.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,15 @@
from writer.core.write_request import LockedFieldsJSON


collectionfield_lock_with_filter_schema = {
"type": "object",
"properties": {
"position": {"type": "integer"},
"filter": filter_definitions_schema,
},
"required": ["position"],
}

write_schema = fastjsonschema.compile(
{
"$schema": "http://json-schema.org/draft-07/schema#",
Expand All @@ -33,12 +42,10 @@
"locked_fields": {
"type": "object",
"additionalProperties": {
"type": ["integer", "object"],
"properties": {
"position": {"type": "integer"},
"filter": filter_definitions_schema,
},
"required": ["position", "filter"],
**collectionfield_lock_with_filter_schema,
"type": ["integer", "object", "array"],
"items": collectionfield_lock_with_filter_schema,
"minItems": 1,
},
},
"events": {
Expand Down
27 changes: 13 additions & 14 deletions writer/writer/postgresql_backend/sql_occ_locker_backend_service.py
Original file line number Diff line number Diff line change
Expand Up @@ -117,21 +117,20 @@ def assert_collectionfield_positions(
)
filter_parts.append("(cf.collectionfield=%s and e.position>%s)")
else:
query_arguments.extend(
(
cf_lock.position,
collectionfield,
for lock in cf_lock:
query_arguments.extend(
(
lock.position,
collectionfield,
)
)
)
filter_part = "(e.position>%s and cf.collectionfield=%s"
filter_part += (
" and "
+ self.query_helper.build_filter_str(
cf_lock.filter, query_arguments, "m"
)
+ ")"
)
filter_parts.append(filter_part)
filter_part = "(e.position>%s and cf.collectionfield=%s"
if lock.filter:
filter_part += " and " + self.query_helper.build_filter_str(
lock.filter, query_arguments, "m"
)
filter_part += ")"
filter_parts.append(filter_part)

filter_query = " or ".join(filter_parts)
query = dedent(
Expand Down

0 comments on commit 488b224

Please sign in to comment.