Skip to content

Commit

Permalink
Add ability to deserialize PseudoFunction from json (#378)
Browse files Browse the repository at this point in the history
* Add ability to deserialize PseudoFunction from json
* Support both str and dict in from_json method
  • Loading branch information
bjornandre authored Apr 11, 2024
1 parent 906aa91 commit ca5bcf9
Show file tree
Hide file tree
Showing 3 changed files with 107 additions and 6 deletions.
1 change: 1 addition & 0 deletions src/dapla_pseudo/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@

from dapla_pseudo.v1.api_models import Field
from dapla_pseudo.v1.api_models import PseudoKeyset
from dapla_pseudo.v1.api_models import PseudoRule
from dapla_pseudo.v1.client import PseudoClient
from dapla_pseudo.v1.depseudo import Depseudonymize
from dapla_pseudo.v1.pseudo import Pseudonymize
Expand Down
52 changes: 52 additions & 0 deletions src/dapla_pseudo/v1/api_models.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
from pydantic import ValidationError
from pydantic import field_serializer
from pydantic import model_serializer
from pydantic import model_validator

from dapla_pseudo.constants import MapFailureStrategy
from dapla_pseudo.constants import PredefinedKeys
Expand Down Expand Up @@ -191,6 +192,49 @@ def serialize_model(self) -> str:
"""Serialize the function as expected by the pseudo service."""
return f"{self.function_type}({self.kwargs})"

@model_validator(mode="before")
@classmethod
def deserialize_model(cls, data: str | dict[str, t.Any]) -> dict[str, t.Any]:
"""Deserialize the shorthand string representation of a pseudo function to Python model.
This function parses the serialized version of a function like e.g. 'redact(placeholder=#)'
by splitting the function name (fun) from the arguments (args), and then constructing a
dict out of the args. Finally, the proper function type and kwargs are inferred from the
PseudoFunctionTypes enum.
"""
if isinstance(data, str):
func: str
args: str
func, args = (
data.replace(" ", "").replace("(", " ").replace(")", "").split(" ")
)
pseudo_function_type: PseudoFunctionTypes = PseudoFunctionTypes[
func.upper()
]
args_dict: dict[str, str] = dict(
list(map(lambda v: v.split("="), args.split(",")))
)
return {
"function_type": pseudo_function_type,
"kwargs": cls._resolve_args(pseudo_function_type, args_dict),
}
else:
return data

@classmethod
def _resolve_args(
cls, pseudo_function_type: PseudoFunctionTypes, args: dict[str, str]
) -> DaeadKeywordArgs | FF31KeywordArgs | MapSidKeywordArgs | RedactKeywordArgs:
match pseudo_function_type:
case PseudoFunctionTypes.DAEAD:
return DaeadKeywordArgs.model_validate(args)
case PseudoFunctionTypes.REDACT:
return RedactKeywordArgs.model_validate(args)
case PseudoFunctionTypes.FF31:
return FF31KeywordArgs.model_validate(args)
case PseudoFunctionTypes.MAP_SID:
return MapSidKeywordArgs.model_validate(args)


class PseudoRule(APIModel):
"""A ``PseudoRule`` defines a pattern, a transformation function, and optionally a friendly name of the rule.
Expand Down Expand Up @@ -218,6 +262,14 @@ def serialize_func(
"""Explicit serialization of the 'func' field to coerce to string before serializing PseudoRule."""
return str(func)

@classmethod
def from_json(cls, data: str | dict[str, t.Any]) -> t.Any:
"""Deserialise the json-formatted pseudo rule to Python model."""
if isinstance(data, str):
return super().model_validate(json.loads(data))
else:
return super().model_validate(data)


class PseudoFieldRequest(APIModel):
"""Model of the pseudo field request sent to the service."""
Expand Down
60 changes: 54 additions & 6 deletions tests/v1/test_api_models.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
from dapla_pseudo.v1.api_models import KeyWrapper
from dapla_pseudo.v1.api_models import PseudoFunction
from dapla_pseudo.v1.api_models import PseudoKeyset
from dapla_pseudo.v1.api_models import PseudoRule
from dapla_pseudo.v1.api_models import RedactKeywordArgs

TEST_FILE_PATH = "tests/v1/test_files"
Expand Down Expand Up @@ -61,27 +62,74 @@ def test_key_wrapper_with_keyset_json() -> None:
assert key_wrapper.keyset == PseudoKeyset.model_validate(custom_keyset_dict)


def test_pseudo_function() -> None:
assert "daead(keyId=ssb-common-key-1)" == str(
def test_serialize_daead_function() -> None:
assert str(
PseudoFunction(
function_type=PseudoFunctionTypes.DAEAD, kwargs=DaeadKeywordArgs()
)
== "daead(keyId=ssb-common-key-1)"
)


def test_redact_function() -> None:
assert "redact(placeholder=#)" == str(
def test_deserialize_daead_function() -> None:
assert PseudoFunction.model_validate("daead(keyId=ssb-common-key-1)") == (
PseudoFunction(
function_type=PseudoFunctionTypes.DAEAD, kwargs=DaeadKeywordArgs()
)
)


def test_serialize_redact_function() -> None:
assert str(
PseudoFunction(
function_type=PseudoFunctionTypes.REDACT,
kwargs=RedactKeywordArgs(placeholder="#"),
)
== "redact(placeholder=#)"
)


def test_deserialize_redact_function() -> None:
assert PseudoFunction.model_validate("redact(placeholder=#)") == (
PseudoFunction(
function_type=PseudoFunctionTypes.REDACT,
kwargs=RedactKeywordArgs(placeholder="#"),
)
)


def test_pseudo_function_with_extra_kwargs() -> None:
assert "ff31(keyId=papis-common-key-1,strategy=skip)" == str(
def test_serialize_function_with_extra_kwargs() -> None:
assert str(
PseudoFunction(
function_type=PseudoFunctionTypes.FF31,
kwargs=FF31KeywordArgs(),
)
== "ff31(keyId=papis-common-key-1,strategy=skip)"
)


def test_deserialize_function_with_extra_kwargs() -> None:
assert PseudoFunction.model_validate(
"ff31(keyId=papis-common-key-1,strategy=skip)"
) == (
PseudoFunction(
function_type=PseudoFunctionTypes.FF31,
kwargs=FF31KeywordArgs(),
)
)


def test_deserialize_pseudo_rule() -> None:
assert PseudoRule.from_json(
'{"name":"my-fule","pattern":"**/identifiers/*","func":"ff31('
'keyId=papis-common-key-1,strategy=skip)"}'
) == (
PseudoRule(
name="my-fule",
func=PseudoFunction(
function_type=PseudoFunctionTypes.FF31,
kwargs=FF31KeywordArgs(),
),
pattern="**/identifiers/*",
)
)

0 comments on commit ca5bcf9

Please sign in to comment.