From 9fb8d52a7d58624c7d79673f10ded96f8f3ec300 Mon Sep 17 00:00:00 2001 From: YooSunyoung Date: Fri, 29 Sep 2023 15:09:08 +0200 Subject: [PATCH 01/13] Dict-like methods --- src/scitacean/dataset.py | 108 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 108 insertions(+) diff --git a/src/scitacean/dataset.py b/src/scitacean/dataset.py index 8fe2ad5c..99743eed 100644 --- a/src/scitacean/dataset.py +++ b/src/scitacean/dataset.py @@ -507,6 +507,114 @@ def validate(self) -> None: """ self.make_upload_model() + def keys(self) -> Iterable[str]: + """Dict-like keys(names of fields) method. + + Returns + ------- + : + Generator of names of all fields corresponding to ``self.type`` + and other fields that are not ``None``. + """ + from itertools import chain + + all_fields = set((field.name for field in self.fields())) + my_fields = set((field.name for field in self.fields(dataset_type=self.type))) + other_fields = all_fields - my_fields + invalid_fields = ( + f_name for f_name in other_fields if getattr(self, f_name) is not None + ) + + return chain(my_fields, invalid_fields) + + def values(self) -> Iterable[Any]: + """Dict-like values(values of fields) method. + + Returns + ------- + : + Generator of values of all fields corresponding to ``self.type`` + and other fields that are not ``None``. + """ + return (getattr(self, field_name) for field_name in self.keys()) + + def items(self) -> Iterable[tuple[str, Any]]: + """Dict-like items(name and value pairs of fields) method. + + Returns + ------- + : + Generator of (Name, Value) pairs of all fields + corresponding to ``self.type`` + and other fields that are not ``None``. + """ + return ((key, getattr(self, key)) for key in self.keys()) + + @classmethod + def _validate_field_name(cls, field_name: str) -> None: + """Validate ``field_name``. + + If ``field_name`` is a ``name`` of any + :class:`DatasetBase.Field` objects in ``self.fields()``. + + Raises + ------ + : + :class:`KeyError` if validation fails. + """ + if field_name not in (field.name for field in cls.fields()): + raise KeyError(f"{field_name} is not a valid field name.") + + def __getitem__(self, field_name: str) -> Any: + """Dict-like get-item method. + + Returns + ------- + : + Value of the field with the name ``field_name``. + + Raises + ------ + : + :class:`KeyError` if ``field_name`` does not mach any names of fields. + """ + self._validate_field_name(field_name) + return getattr(self, field_name) + + def __setitem__(self, field_name: str, field_value: Any) -> None: + """Dict-like set-item method. + + Set the value of the field with name ``field_name`` as ``field_value``. + + Raises + ------ + : + :class:`KeyError` if ``field_name`` does not mach any names of fields. + """ + self._validate_field_name(field_name) + setattr(self, field_name, field_value) + + def setdefault(self, field_name: str, default_value: Any) -> Any: + """Dict-like setdefault method. + + Set the value of the field with name ``field_name`` as ``default_value`` + only if value of ``field_name`` is ``None``. + + Returns + ------- + : + Value of the field with name ``field_name``. + + Raises + ------ + : + :class:`KeyError` if ``field_name`` does not mach any names of fields. + """ + self._validate_field_name(field_name) + if getattr(self, field_name) is None: + setattr(self, field_name, default_value) + return getattr(self, field_name) + @dataclasses.dataclass class DatablockUploadModels: From fd190ecd2c05f40da60a7cd27247abdc22f0c944 Mon Sep 17 00:00:00 2001 From: YooSunyoung Date: Fri, 29 Sep 2023 15:09:24 +0200 Subject: [PATCH 02/13] Dict-like methods tests --- tests/dataset_test.py | 78 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 78 insertions(+) diff --git a/tests/dataset_test.py b/tests/dataset_test.py index 9a183d0c..d48ea7d4 100644 --- a/tests/dataset_test.py +++ b/tests/dataset_test.py @@ -765,3 +765,81 @@ def test_derive_removes_attachments(initial, attachments): initial.attachments = attachments derived = initial.derive() assert derived.attachments == [] + + +@pytest.fixture(params=[DatasetType.RAW, DatasetType.DERIVED]) +def my_type(request): + return request.param + + +@pytest.fixture +def invalid_field_example(my_type) -> tuple[str, str]: + if my_type == DatasetType.DERIVED: + return "data_format", "sth_not_None" + elif my_type == DatasetType.RAW: + return "job_log_data", "sth_not_None" + else: + raise ValueError(my_type, " is not valid DatasetType.") + + +def test_dataset_dict_like_keys_per_type(my_type): + ds = Dataset(type=my_type) + my_names = set( + field.name for field in Dataset._FIELD_SPEC if field.used_by(my_type) + ) + assert set(ds.keys()) == my_names + + +def test_dataset_dict_like_keys_including_invalid_field( + my_type, invalid_field_example: tuple[str, str] +): + invalid_name, invalid_value = invalid_field_example + + my_names = set( + field.name for field in Dataset._FIELD_SPEC if field.used_by(my_type) + ) + assert invalid_name not in my_names + my_names.add(invalid_name) + + ds = Dataset(type=my_type) + setattr(ds, invalid_name, invalid_value) + + assert set(ds.keys()) == my_names + + +def test_dataset_dict_like_values(my_type): + ds = Dataset(type=my_type, comment="This is an example.") + for key, value in zip(ds.keys(), ds.values()): + assert value == getattr(ds, key) + + +def test_dataset_dict_like_values_with_invalid_field( + my_type, invalid_field_example: tuple[str, str] +): + ds = Dataset(type=my_type, comment="This is an example.") + setattr(ds, *invalid_field_example) + for key, value in zip(ds.keys(), ds.values()): + assert value == getattr(ds, key) + + +def test_dataset_dict_like_items_with_invalid_field( + my_type, invalid_field_example: tuple[str, str] +): + ds = Dataset(type=my_type, comment="This is an example.") + setattr(ds, *invalid_field_example) + for key, value in ds.items(): + assert value == getattr(ds, key) + + +def test_dataset_dict_like_getitem(my_type): + ds = Dataset(type=my_type) + assert ds["type"] == my_type + assert ds["comment"] is None + + +def test_dataset_dict_like_setdefault(my_type): + sample_comment = "This is an example." + ds = Dataset(type=my_type) + assert ds["comment"] is None + assert ds.setdefault("comment", sample_comment) == sample_comment + assert ds["comment"] == sample_comment From bad61a957aaebb6b6573b3623478f28a392d1696 Mon Sep 17 00:00:00 2001 From: YooSunyoung Date: Mon, 2 Oct 2023 11:58:01 +0200 Subject: [PATCH 03/13] Add dict-like get/setitem and setdefault tests including wrong/invalid fields cases. --- tests/dataset_test.py | 63 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 63 insertions(+) diff --git a/tests/dataset_test.py b/tests/dataset_test.py index d48ea7d4..68a64a5b 100644 --- a/tests/dataset_test.py +++ b/tests/dataset_test.py @@ -837,9 +837,72 @@ def test_dataset_dict_like_getitem(my_type): assert ds["comment"] is None +@pytest.mark.parametrize( + ("is_attr", "wrong_field"), ((True, "size"), (False, "OBVIOUSLYWRONGNAME")) +) +def test_dataset_dict_like_getitem_wrong_field_raises(is_attr, wrong_field): + # 'size' should be included in the field later. + # It is now excluded because it is ``manual`` field. See issue#151. + ds = Dataset(type="raw") + assert hasattr(ds, wrong_field) == is_attr + with pytest.raises(KeyError, match=f"{wrong_field} is not a valid field name."): + ds[wrong_field] + + def test_dataset_dict_like_setdefault(my_type): sample_comment = "This is an example." ds = Dataset(type=my_type) assert ds["comment"] is None assert ds.setdefault("comment", sample_comment) == sample_comment assert ds["comment"] == sample_comment + + +def test_dataset_dict_like_setdefault_existing_key(): + original_comment = "This is the original comment." + default_comment = "This is an example." + ds = Dataset(type="raw", comment=original_comment) + assert ds["comment"] == original_comment + assert ds.setdefault("comment", default_comment) == original_comment + + +def test_dataset_dict_like_setdefault_object(): + ds = Dataset(type="raw") + assert ds["shared_with"] is None + default_list = [] + shared_with = ds.setdefault("shared_with", default_list) + assert default_list is shared_with + assert ds["shared_with"] is default_list + + +def test_dataset_dict_like_setitem(my_type): + sample_comment = "This is an example." + ds = Dataset(type=my_type) + assert ds["comment"] is None + ds["comment"] = sample_comment + assert ds["comment"] == sample_comment + + +def test_dataset_dict_like_setitem_invalid_field( + my_type, invalid_field_example: tuple[str, str] +): + # ``__setitem__`` doesn't check if the item is invalid for the current type or not. + ds = Dataset(type=my_type) + invalid_field, invalid_value = invalid_field_example + assert ds[invalid_field] is None + ds[invalid_field] = invalid_value + assert ds[invalid_field] == invalid_value + + +@pytest.mark.parametrize( + ("is_attr", "wrong_field", "wrong_value"), + ((True, "size", 10), (False, "OBVIOUSLYWRONGNAME", "OBVIOUSLYWRONGVALUE")), +) +def test_dataset_dict_like_setitem_wrong_field_raises( + is_attr, wrong_field, wrong_value +): + # ``manual`` fields such as ``size`` should raise with ``__setitem__``. + # However, it may need more specific error message. + ds = Dataset(type="raw") + assert hasattr(ds, wrong_field) == is_attr + with pytest.raises(KeyError, match=f"{wrong_field} is not a valid field name."): + ds[wrong_field] = wrong_value From 0e5ac326ecabb7569f4f89f502cbdd80484b716f Mon Sep 17 00:00:00 2001 From: YooSunyoung Date: Mon, 2 Oct 2023 12:44:05 +0200 Subject: [PATCH 04/13] Update docstring of dataset --- src/scitacean/dataset.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/scitacean/dataset.py b/src/scitacean/dataset.py index 99743eed..0e3d67e5 100644 --- a/src/scitacean/dataset.py +++ b/src/scitacean/dataset.py @@ -598,7 +598,7 @@ def setdefault(self, field_name: str, default_value: Any) -> Any: """Dict-like setdefault method. Set the value of the field with name ``field_name`` as ``default_value`` - only if value of ``field_name`` is ``None``. + only if the value of ``field_name`` is ``None``. Returns ------- From 5bd086af3242ac7209f27dd8d9f549b9fde0d721 Mon Sep 17 00:00:00 2001 From: YooSunyoung Date: Mon, 2 Oct 2023 12:56:44 +0200 Subject: [PATCH 05/13] Remove unsupported type hints in py38 --- tests/dataset_test.py | 18 +++++------------- 1 file changed, 5 insertions(+), 13 deletions(-) diff --git a/tests/dataset_test.py b/tests/dataset_test.py index 68a64a5b..1b65d9d4 100644 --- a/tests/dataset_test.py +++ b/tests/dataset_test.py @@ -773,7 +773,7 @@ def my_type(request): @pytest.fixture -def invalid_field_example(my_type) -> tuple[str, str]: +def invalid_field_example(my_type): if my_type == DatasetType.DERIVED: return "data_format", "sth_not_None" elif my_type == DatasetType.RAW: @@ -790,9 +790,7 @@ def test_dataset_dict_like_keys_per_type(my_type): assert set(ds.keys()) == my_names -def test_dataset_dict_like_keys_including_invalid_field( - my_type, invalid_field_example: tuple[str, str] -): +def test_dataset_dict_like_keys_including_invalid_field(my_type, invalid_field_example): invalid_name, invalid_value = invalid_field_example my_names = set( @@ -813,18 +811,14 @@ def test_dataset_dict_like_values(my_type): assert value == getattr(ds, key) -def test_dataset_dict_like_values_with_invalid_field( - my_type, invalid_field_example: tuple[str, str] -): +def test_dataset_dict_like_values_with_invalid_field(my_type, invalid_field_example): ds = Dataset(type=my_type, comment="This is an example.") setattr(ds, *invalid_field_example) for key, value in zip(ds.keys(), ds.values()): assert value == getattr(ds, key) -def test_dataset_dict_like_items_with_invalid_field( - my_type, invalid_field_example: tuple[str, str] -): +def test_dataset_dict_like_items_with_invalid_field(my_type, invalid_field_example): ds = Dataset(type=my_type, comment="This is an example.") setattr(ds, *invalid_field_example) for key, value in ds.items(): @@ -882,9 +876,7 @@ def test_dataset_dict_like_setitem(my_type): assert ds["comment"] == sample_comment -def test_dataset_dict_like_setitem_invalid_field( - my_type, invalid_field_example: tuple[str, str] -): +def test_dataset_dict_like_setitem_invalid_field(my_type, invalid_field_example): # ``__setitem__`` doesn't check if the item is invalid for the current type or not. ds = Dataset(type=my_type) invalid_field, invalid_value = invalid_field_example From 6f78d610959c209f3acb24b2e95eeda114d03f60 Mon Sep 17 00:00:00 2001 From: YooSunyoung Date: Mon, 2 Oct 2023 13:27:41 +0200 Subject: [PATCH 06/13] Add versionadded tag with release placeholder and parameters in the docstring --- src/scitacean/dataset.py | 38 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/src/scitacean/dataset.py b/src/scitacean/dataset.py index 0e3d67e5..67028fb1 100644 --- a/src/scitacean/dataset.py +++ b/src/scitacean/dataset.py @@ -510,6 +510,8 @@ def validate(self) -> None: def keys(self) -> Iterable[str]: """Dict-like keys(names of fields) method. + .. versionadded:: RELEASE_PLACEHOLDER + Returns ------- : @@ -530,6 +532,8 @@ def keys(self) -> Iterable[str]: def values(self) -> Iterable[Any]: """Dict-like values(values of fields) method. + .. versionadded:: RELEASE_PLACEHOLDER + Returns ------- : @@ -541,6 +545,8 @@ def values(self) -> Iterable[Any]: def items(self) -> Iterable[tuple[str, Any]]: """Dict-like items(name and value pairs of fields) method. + .. versionadded:: RELEASE_PLACEHOLDER + Returns ------- : @@ -557,6 +563,11 @@ def _validate_field_name(cls, field_name: str) -> None: If ``field_name`` is a ``name`` of any :class:`DatasetBase.Field` objects in ``self.fields()``. + Parameters + ---------- + field_name: + Name of the field to validate. + Raises ------ : @@ -568,6 +579,13 @@ def _validate_field_name(cls, field_name: str) -> None: def __getitem__(self, field_name: str) -> Any: """Dict-like get-item method. + .. versionadded:: RELEASE_PLACEHOLDER + + Parameters + ---------- + field_name: + Name of the field to retrieve. + Returns ------- : @@ -586,6 +604,16 @@ def __setitem__(self, field_name: str, field_value: Any) -> None: Set the value of the field with name ``field_name`` as ``field_value``. + .. versionadded:: RELEASE_PLACEHOLDER + + Parameters + ---------- + field_name: + Name of the field to set. + + default_value: + Value of the field to set. + Raises ------ : @@ -600,6 +628,16 @@ def setdefault(self, field_name: str, default_value: Any) -> Any: Set the value of the field with name ``field_name`` as ``default_value`` only if the value of ``field_name`` is ``None``. + .. versionadded:: RELEASE_PLACEHOLDER + + Parameters + ---------- + field_name: + Name of the field to retrieve or set a default value if needed. + + default_value: + Value of the field to set if not set (is ``None``). + Returns ------- : From 0553f71955c214c835e9877ff1f3eabc1c031b81 Mon Sep 17 00:00:00 2001 From: Sunyoung Yoo Date: Tue, 3 Oct 2023 12:56:22 +0200 Subject: [PATCH 07/13] Update src/scitacean/dataset.py Co-authored-by: Jan-Lukas Wynen --- src/scitacean/dataset.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/scitacean/dataset.py b/src/scitacean/dataset.py index 67028fb1..b8e4539d 100644 --- a/src/scitacean/dataset.py +++ b/src/scitacean/dataset.py @@ -570,8 +570,8 @@ def _validate_field_name(cls, field_name: str) -> None: Raises ------ - : - :class:`KeyError` if validation fails. + KeyError + If validation fails. """ if field_name not in (field.name for field in cls.fields()): raise KeyError(f"{field_name} is not a valid field name.") From 294f4bc6c1e3f5619a596966cc12c7a01a4e24bb Mon Sep 17 00:00:00 2001 From: Sunyoung Yoo Date: Tue, 3 Oct 2023 12:56:30 +0200 Subject: [PATCH 08/13] Update src/scitacean/dataset.py Co-authored-by: Jan-Lukas Wynen --- src/scitacean/dataset.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/scitacean/dataset.py b/src/scitacean/dataset.py index b8e4539d..90534ffa 100644 --- a/src/scitacean/dataset.py +++ b/src/scitacean/dataset.py @@ -611,7 +611,7 @@ def __setitem__(self, field_name: str, field_value: Any) -> None: field_name: Name of the field to set. - default_value: + field_value: Value of the field to set. Raises From dbfbd5f72ed050763c403d354577675eddb5c907 Mon Sep 17 00:00:00 2001 From: YooSunyoung Date: Tue, 3 Oct 2023 13:49:56 +0200 Subject: [PATCH 09/13] Use dataset strategies instead of separate type fixture --- tests/dataset_test.py | 136 ++++++++++++++++++------------------------ 1 file changed, 58 insertions(+), 78 deletions(-) diff --git a/tests/dataset_test.py b/tests/dataset_test.py index 1b65d9d4..876163a1 100644 --- a/tests/dataset_test.py +++ b/tests/dataset_test.py @@ -767,12 +767,6 @@ def test_derive_removes_attachments(initial, attachments): assert derived.attachments == [] -@pytest.fixture(params=[DatasetType.RAW, DatasetType.DERIVED]) -def my_type(request): - return request.param - - -@pytest.fixture def invalid_field_example(my_type): if my_type == DatasetType.DERIVED: return "data_format", "sth_not_None" @@ -782,119 +776,105 @@ def invalid_field_example(my_type): raise ValueError(my_type, " is not valid DatasetType.") -def test_dataset_dict_like_keys_per_type(my_type): - ds = Dataset(type=my_type) +@given(initial=sst.datasets(for_upload=True)) +@settings(max_examples=10) +def test_dataset_dict_like_keys_per_type(initial: Dataset): my_names = set( - field.name for field in Dataset._FIELD_SPEC if field.used_by(my_type) + field.name for field in Dataset._FIELD_SPEC if field.used_by(initial.type) ) - assert set(ds.keys()) == my_names + assert set(initial.keys()) == my_names -def test_dataset_dict_like_keys_including_invalid_field(my_type, invalid_field_example): - invalid_name, invalid_value = invalid_field_example +@given(initial=sst.datasets(for_upload=True)) +@settings(max_examples=10) +def test_dataset_dict_like_keys_including_invalid_field(initial): + invalid_name, invalid_value = invalid_field_example(initial.type) my_names = set( - field.name for field in Dataset._FIELD_SPEC if field.used_by(my_type) + field.name for field in Dataset._FIELD_SPEC if field.used_by(initial.type) ) assert invalid_name not in my_names my_names.add(invalid_name) - ds = Dataset(type=my_type) - setattr(ds, invalid_name, invalid_value) + setattr(initial, invalid_name, invalid_value) - assert set(ds.keys()) == my_names + assert set(initial.keys()) == my_names -def test_dataset_dict_like_values(my_type): - ds = Dataset(type=my_type, comment="This is an example.") - for key, value in zip(ds.keys(), ds.values()): - assert value == getattr(ds, key) +@given(initial=sst.datasets(for_upload=True)) +@settings(max_examples=10) +def test_dataset_dict_like_values(initial: Dataset): + for key, value in zip(initial.keys(), initial.values()): + assert value == getattr(initial, key) -def test_dataset_dict_like_values_with_invalid_field(my_type, invalid_field_example): - ds = Dataset(type=my_type, comment="This is an example.") - setattr(ds, *invalid_field_example) - for key, value in zip(ds.keys(), ds.values()): - assert value == getattr(ds, key) +@given(initial=sst.datasets(for_upload=True)) +@settings(max_examples=10) +def test_dataset_dict_like_values_with_invalid_field(initial: Dataset): + setattr(initial, *invalid_field_example(initial.type)) + for key, value in zip(initial.keys(), initial.values()): + assert value == getattr(initial, key) -def test_dataset_dict_like_items_with_invalid_field(my_type, invalid_field_example): - ds = Dataset(type=my_type, comment="This is an example.") - setattr(ds, *invalid_field_example) - for key, value in ds.items(): - assert value == getattr(ds, key) +@given(initial=sst.datasets(for_upload=True)) +@settings(max_examples=10) +def test_dataset_dict_like_items_with_invalid_field(initial: Dataset): + setattr(initial, *invalid_field_example(initial.type)) + for key, value in initial.items(): + assert value == getattr(initial, key) -def test_dataset_dict_like_getitem(my_type): - ds = Dataset(type=my_type) - assert ds["type"] == my_type - assert ds["comment"] is None +@given(initial=sst.datasets(for_upload=True)) +@settings(max_examples=10) +def test_dataset_dict_like_getitem(initial): + assert initial["type"] == initial.type @pytest.mark.parametrize( ("is_attr", "wrong_field"), ((True, "size"), (False, "OBVIOUSLYWRONGNAME")) ) -def test_dataset_dict_like_getitem_wrong_field_raises(is_attr, wrong_field): +@given(initial=sst.datasets(for_upload=True)) +@settings(max_examples=10) +def test_dataset_dict_like_getitem_wrong_field_raises(initial, is_attr, wrong_field): # 'size' should be included in the field later. # It is now excluded because it is ``manual`` field. See issue#151. - ds = Dataset(type="raw") - assert hasattr(ds, wrong_field) == is_attr + assert hasattr(initial, wrong_field) == is_attr with pytest.raises(KeyError, match=f"{wrong_field} is not a valid field name."): - ds[wrong_field] + initial[wrong_field] -def test_dataset_dict_like_setdefault(my_type): - sample_comment = "This is an example." - ds = Dataset(type=my_type) - assert ds["comment"] is None - assert ds.setdefault("comment", sample_comment) == sample_comment - assert ds["comment"] == sample_comment - - -def test_dataset_dict_like_setdefault_existing_key(): - original_comment = "This is the original comment." - default_comment = "This is an example." - ds = Dataset(type="raw", comment=original_comment) - assert ds["comment"] == original_comment - assert ds.setdefault("comment", default_comment) == original_comment - - -def test_dataset_dict_like_setdefault_object(): - ds = Dataset(type="raw") - assert ds["shared_with"] is None - default_list = [] - shared_with = ds.setdefault("shared_with", default_list) - assert default_list is shared_with - assert ds["shared_with"] is default_list - +@given(initial=sst.datasets(for_upload=True)) +@settings(max_examples=10) +def test_dataset_dict_like_setitem(initial: Dataset): + import uuid -def test_dataset_dict_like_setitem(my_type): - sample_comment = "This is an example." - ds = Dataset(type=my_type) - assert ds["comment"] is None - ds["comment"] = sample_comment - assert ds["comment"] == sample_comment + sample_comment = uuid.uuid4().hex + assert initial["comment"] != sample_comment + initial["comment"] = sample_comment + assert initial["comment"] == sample_comment -def test_dataset_dict_like_setitem_invalid_field(my_type, invalid_field_example): +@given(initial=sst.datasets(for_upload=True)) +@settings(max_examples=10) +def test_dataset_dict_like_setitem_invalid_field(initial: Dataset): # ``__setitem__`` doesn't check if the item is invalid for the current type or not. - ds = Dataset(type=my_type) - invalid_field, invalid_value = invalid_field_example - assert ds[invalid_field] is None - ds[invalid_field] = invalid_value - assert ds[invalid_field] == invalid_value + invalid_field, invalid_value = invalid_field_example(initial.type) + assert initial[invalid_field] is None + initial[invalid_field] = invalid_value + assert initial[invalid_field] == invalid_value @pytest.mark.parametrize( ("is_attr", "wrong_field", "wrong_value"), ((True, "size", 10), (False, "OBVIOUSLYWRONGNAME", "OBVIOUSLYWRONGVALUE")), ) +@given(initial=sst.datasets(for_upload=True)) +@settings(max_examples=10) def test_dataset_dict_like_setitem_wrong_field_raises( - is_attr, wrong_field, wrong_value + initial, is_attr, wrong_field, wrong_value ): # ``manual`` fields such as ``size`` should raise with ``__setitem__``. # However, it may need more specific error message. - ds = Dataset(type="raw") - assert hasattr(ds, wrong_field) == is_attr + assert hasattr(initial, wrong_field) == is_attr with pytest.raises(KeyError, match=f"{wrong_field} is not a valid field name."): - ds[wrong_field] = wrong_value + initial[wrong_field] = wrong_value From a1b60b99eb967654bae81d4b4e08f6b3e6e027a7 Mon Sep 17 00:00:00 2001 From: YooSunyoung Date: Tue, 3 Oct 2023 13:50:09 +0200 Subject: [PATCH 10/13] Remove setdefault interface --- src/scitacean/dataset.py | 49 ++++++++-------------------------------- 1 file changed, 9 insertions(+), 40 deletions(-) diff --git a/src/scitacean/dataset.py b/src/scitacean/dataset.py index 90534ffa..f38f048d 100644 --- a/src/scitacean/dataset.py +++ b/src/scitacean/dataset.py @@ -510,13 +510,13 @@ def validate(self) -> None: def keys(self) -> Iterable[str]: """Dict-like keys(names of fields) method. - .. versionadded:: RELEASE_PLACEHOLDER - Returns ------- : Generator of names of all fields corresponding to ``self.type`` and other fields that are not ``None``. + + .. versionadded:: RELEASE_PLACEHOLDER """ from itertools import chain @@ -532,27 +532,27 @@ def keys(self) -> Iterable[str]: def values(self) -> Iterable[Any]: """Dict-like values(values of fields) method. - .. versionadded:: RELEASE_PLACEHOLDER - Returns ------- : Generator of values of all fields corresponding to ``self.type`` and other fields that are not ``None``. + + .. versionadded:: RELEASE_PLACEHOLDER """ return (getattr(self, field_name) for field_name in self.keys()) def items(self) -> Iterable[tuple[str, Any]]: """Dict-like items(name and value pairs of fields) method. - .. versionadded:: RELEASE_PLACEHOLDER - Returns ------- : Generator of (Name, Value) pairs of all fields corresponding to ``self.type`` and other fields that are not ``None``. + + .. versionadded:: RELEASE_PLACEHOLDER """ return ((key, getattr(self, key)) for key in self.keys()) @@ -579,8 +579,6 @@ def _validate_field_name(cls, field_name: str) -> None: def __getitem__(self, field_name: str) -> Any: """Dict-like get-item method. - .. versionadded:: RELEASE_PLACEHOLDER - Parameters ---------- field_name: @@ -595,6 +593,8 @@ def __getitem__(self, field_name: str) -> Any: ------ : :class:`KeyError` if ``field_name`` does not mach any names of fields. + + .. versionadded:: RELEASE_PLACEHOLDER """ self._validate_field_name(field_name) return getattr(self, field_name) @@ -604,8 +604,6 @@ def __setitem__(self, field_name: str, field_value: Any) -> None: Set the value of the field with name ``field_name`` as ``field_value``. - .. versionadded:: RELEASE_PLACEHOLDER - Parameters ---------- field_name: @@ -618,40 +616,11 @@ def __setitem__(self, field_name: str, field_value: Any) -> None: ------ : :class:`KeyError` if ``field_name`` does not mach any names of fields. - """ - self._validate_field_name(field_name) - setattr(self, field_name, field_value) - - def setdefault(self, field_name: str, default_value: Any) -> Any: - """Dict-like setdefault method. - - Set the value of the field with name ``field_name`` as ``default_value`` - only if the value of ``field_name`` is ``None``. .. versionadded:: RELEASE_PLACEHOLDER - - Parameters - ---------- - field_name: - Name of the field to retrieve or set a default value if needed. - - default_value: - Value of the field to set if not set (is ``None``). - - Returns - ------- - : - Value of the field with name ``field_name``. - - Raises - ------ - : - :class:`KeyError` if ``field_name`` does not mach any names of fields. """ self._validate_field_name(field_name) - if getattr(self, field_name) is None: - setattr(self, field_name, default_value) - return getattr(self, field_name) + setattr(self, field_name, field_value) @dataclasses.dataclass From 21b0e4093d3ed053c3f939fdb241fce0d6190f12 Mon Sep 17 00:00:00 2001 From: YooSunyoung Date: Tue, 3 Oct 2023 14:59:03 +0200 Subject: [PATCH 11/13] Update releasnote about new features, dict-like methods --- docs/release-notes.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/docs/release-notes.rst b/docs/release-notes.rst index 96342da8..23329aac 100644 --- a/docs/release-notes.rst +++ b/docs/release-notes.rst @@ -41,6 +41,9 @@ Security Features ~~~~~~~~ +* Added dict-like methods, :meth:`Dataset.keys`, :meth:`Dataset.values` and :meth:`Dataset.items`. +* Added dict-like item setter/getter, :meth:`Dataset.__getitem__` and :meth:`Dataset.__setitem__`. + Breaking changes ~~~~~~~~~~~~~~~~ From d9ff2dcac36e61e12c56f3daad2773ffb93b286c Mon Sep 17 00:00:00 2001 From: YooSunyoung Date: Tue, 3 Oct 2023 14:59:22 +0200 Subject: [PATCH 12/13] Move versionadded mark above the sub titles. --- src/scitacean/dataset.py | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/src/scitacean/dataset.py b/src/scitacean/dataset.py index f38f048d..a77a04bb 100644 --- a/src/scitacean/dataset.py +++ b/src/scitacean/dataset.py @@ -510,13 +510,13 @@ def validate(self) -> None: def keys(self) -> Iterable[str]: """Dict-like keys(names of fields) method. + .. versionadded:: RELEASE_PLACEHOLDER + Returns ------- : Generator of names of all fields corresponding to ``self.type`` and other fields that are not ``None``. - - .. versionadded:: RELEASE_PLACEHOLDER """ from itertools import chain @@ -532,27 +532,27 @@ def keys(self) -> Iterable[str]: def values(self) -> Iterable[Any]: """Dict-like values(values of fields) method. + .. versionadded:: RELEASE_PLACEHOLDER + Returns ------- : Generator of values of all fields corresponding to ``self.type`` and other fields that are not ``None``. - - .. versionadded:: RELEASE_PLACEHOLDER """ return (getattr(self, field_name) for field_name in self.keys()) def items(self) -> Iterable[tuple[str, Any]]: """Dict-like items(name and value pairs of fields) method. + .. versionadded:: RELEASE_PLACEHOLDER + Returns ------- : Generator of (Name, Value) pairs of all fields corresponding to ``self.type`` and other fields that are not ``None``. - - .. versionadded:: RELEASE_PLACEHOLDER """ return ((key, getattr(self, key)) for key in self.keys()) @@ -579,6 +579,8 @@ def _validate_field_name(cls, field_name: str) -> None: def __getitem__(self, field_name: str) -> Any: """Dict-like get-item method. + .. versionadded:: RELEASE_PLACEHOLDER + Parameters ---------- field_name: @@ -593,8 +595,6 @@ def __getitem__(self, field_name: str) -> Any: ------ : :class:`KeyError` if ``field_name`` does not mach any names of fields. - - .. versionadded:: RELEASE_PLACEHOLDER """ self._validate_field_name(field_name) return getattr(self, field_name) @@ -604,6 +604,8 @@ def __setitem__(self, field_name: str, field_value: Any) -> None: Set the value of the field with name ``field_name`` as ``field_value``. + .. versionadded:: RELEASE_PLACEHOLDER + Parameters ---------- field_name: @@ -617,7 +619,6 @@ def __setitem__(self, field_name: str, field_value: Any) -> None: : :class:`KeyError` if ``field_name`` does not mach any names of fields. - .. versionadded:: RELEASE_PLACEHOLDER """ self._validate_field_name(field_name) setattr(self, field_name, field_value) From b18ec697514e44157f2c04fbd7f2d79775ef476c Mon Sep 17 00:00:00 2001 From: YooSunyoung Date: Fri, 6 Oct 2023 10:38:57 +0200 Subject: [PATCH 13/13] Move new version tag to the bottom of the docstring --- src/scitacean/dataset.py | 24 ++++++++++++++---------- 1 file changed, 14 insertions(+), 10 deletions(-) diff --git a/src/scitacean/dataset.py b/src/scitacean/dataset.py index a77a04bb..b8de16db 100644 --- a/src/scitacean/dataset.py +++ b/src/scitacean/dataset.py @@ -510,13 +510,14 @@ def validate(self) -> None: def keys(self) -> Iterable[str]: """Dict-like keys(names of fields) method. - .. versionadded:: RELEASE_PLACEHOLDER - Returns ------- : Generator of names of all fields corresponding to ``self.type`` and other fields that are not ``None``. + + + .. versionadded:: RELEASE_PLACEHOLDER """ from itertools import chain @@ -532,27 +533,29 @@ def keys(self) -> Iterable[str]: def values(self) -> Iterable[Any]: """Dict-like values(values of fields) method. - .. versionadded:: RELEASE_PLACEHOLDER - Returns ------- : Generator of values of all fields corresponding to ``self.type`` and other fields that are not ``None``. + + + .. versionadded:: RELEASE_PLACEHOLDER """ return (getattr(self, field_name) for field_name in self.keys()) def items(self) -> Iterable[tuple[str, Any]]: """Dict-like items(name and value pairs of fields) method. - .. versionadded:: RELEASE_PLACEHOLDER - Returns ------- : Generator of (Name, Value) pairs of all fields corresponding to ``self.type`` and other fields that are not ``None``. + + + .. versionadded:: RELEASE_PLACEHOLDER """ return ((key, getattr(self, key)) for key in self.keys()) @@ -579,8 +582,6 @@ def _validate_field_name(cls, field_name: str) -> None: def __getitem__(self, field_name: str) -> Any: """Dict-like get-item method. - .. versionadded:: RELEASE_PLACEHOLDER - Parameters ---------- field_name: @@ -595,6 +596,9 @@ def __getitem__(self, field_name: str) -> Any: ------ : :class:`KeyError` if ``field_name`` does not mach any names of fields. + + + .. versionadded:: RELEASE_PLACEHOLDER """ self._validate_field_name(field_name) return getattr(self, field_name) @@ -604,8 +608,6 @@ def __setitem__(self, field_name: str, field_value: Any) -> None: Set the value of the field with name ``field_name`` as ``field_value``. - .. versionadded:: RELEASE_PLACEHOLDER - Parameters ---------- field_name: @@ -619,6 +621,8 @@ def __setitem__(self, field_name: str, field_value: Any) -> None: : :class:`KeyError` if ``field_name`` does not mach any names of fields. + + .. versionadded:: RELEASE_PLACEHOLDER """ self._validate_field_name(field_name) setattr(self, field_name, field_value)