Skip to content

Commit

Permalink
fix: possible attribute error when adding __ex_id__ (#96)
Browse files Browse the repository at this point in the history
Closes #93

### Summary of Changes

* Treat `numpy` datatypes as primitive, so they don't get wrapped.
* Wrap the addition of the `__ex_id__` into a try-except block to catch
a potential attribute error.
  • Loading branch information
lars-reimann authored Apr 24, 2024
1 parent ff63b3c commit 7cfc0e2
Show file tree
Hide file tree
Showing 3 changed files with 37 additions and 11 deletions.
9 changes: 6 additions & 3 deletions .github/linters/.ruff.toml
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
ignore-init-module-imports = true
line-length = 120
target-version = "py311"

[lint]
ignore-init-module-imports = true

select = [
"F",
"E",
Expand Down Expand Up @@ -44,6 +46,7 @@ select = [
"NPY",
"RUF"
]

ignore = [
# line-too-long (handled by black)
"E501",
Expand Down Expand Up @@ -91,7 +94,7 @@ ignore = [
"TRY003",
]

[per-file-ignores]
[lint.per-file-ignores]
"*test*.py" = [
# Undocumented declarations
"D100",
Expand All @@ -104,5 +107,5 @@ ignore = [
"D107",
]

[pydocstyle]
[lint.pydocstyle]
convention = "numpy"
22 changes: 15 additions & 7 deletions src/safeds_runner/server/_memoization_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@
from multiprocessing.shared_memory import SharedMemory
from typing import Any, TypeAlias

import numpy as np

MemoizationKey: TypeAlias = tuple[str, tuple, tuple]


Expand Down Expand Up @@ -201,7 +203,7 @@ def _is_not_primitive(value: Any) -> bool:
result:
True, if the object is not primitive.
"""
return not isinstance(value, str | int | float | type(None) | bool)
return not isinstance(value, str | int | float | type(None) | bool | np.generic)


def _is_deterministically_hashable(value: Any) -> bool:
Expand Down Expand Up @@ -428,12 +430,18 @@ def _wrap_value_to_shared_memory(
return {_wrap_value_to_shared_memory(entry) for entry in result}
if isinstance(result, frozenset):
return frozenset({_wrap_value_to_shared_memory(entry) for entry in result})
elif _is_deterministically_hashable(result):
_set_new_explicit_identity_deterministic_hash(result)
return ExplicitIdentityWrapperLazy.shared(result)
elif _is_not_primitive(result):
_set_new_explicit_identity(result)
return ExplicitIdentityWrapper.shared(result)

try:
if _is_deterministically_hashable(result):
_set_new_explicit_identity_deterministic_hash(result)
return ExplicitIdentityWrapperLazy.shared(result)
elif _is_not_primitive(result):
_set_new_explicit_identity(result)
return ExplicitIdentityWrapper.shared(result)
except AttributeError:
# We cannot add fields to many built-in types.
pass

return result


Expand Down
17 changes: 16 additions & 1 deletion tests/safeds_runner/server/test_memoization_utils.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
import base64
import datetime
import pickle
import sys
from typing import Any

import numpy as np
import pytest
from safeds.data.image.containers import Image
from safeds.data.tabular.containers import Table
Expand Down Expand Up @@ -308,8 +310,21 @@ def test_wrap_value_to_shared_memory_non_deterministic(value: Any) -> None:
{"a": Table()},
{"a", "b", Table()},
frozenset({"a", "b", Table()}),
np.int64(1),
datetime.date(2021, 1, 1),
],
ids=[
"int",
"list",
"tuple",
"table",
"tuple_table",
"dict",
"set",
"frozenset",
"numpy_int64",
"datetime_date",
],
ids=["int", "list", "tuple", "table", "tuple_table", "dict", "set", "frozenset"],
)
def test_serialize_value_to_shared_memory(value: Any) -> None:
_wrapped = _wrap_value_to_shared_memory(value)
Expand Down

0 comments on commit 7cfc0e2

Please sign in to comment.