From 81686c594feab8113b40265e5ed1b34bff1d55e3 Mon Sep 17 00:00:00 2001 From: Lukas Baecker Date: Fri, 29 Nov 2024 16:12:21 +0100 Subject: [PATCH 1/2] set restored values to default and give user a warning --- field_friend/automations/field.py | 30 ++++++++++++++++--- field_friend/automations/field_provider.py | 7 +++++ tests/old_field_provider_persistence.json | 4 ++- ...ield_provider_persistence_with_errors.json | 19 ++++++++++++ tests/test_field_provider.py | 21 ++++++++++++- 5 files changed, 75 insertions(+), 6 deletions(-) create mode 100644 tests/old_field_provider_persistence_with_errors.json diff --git a/field_friend/automations/field.py b/field_friend/automations/field.py index a4df4704..6026aa0d 100644 --- a/field_friend/automations/field.py +++ b/field_friend/automations/field.py @@ -1,4 +1,5 @@ import math +import uuid from dataclasses import dataclass from typing import Any, Self @@ -161,15 +162,36 @@ def shapely_polygon(self) -> shapely.geometry.Polygon: @classmethod def args_from_dict(cls, data: dict[str, Any]) -> dict: - return data + # Ensure all required fields exist with defaults + defaults: dict[str, Any] = { + 'id': str(uuid.uuid4()), + 'name': 'Field', + 'first_row_start': None, + 'first_row_end': None, + 'row_spacing': 1, + 'row_count': 1, + 'outline_buffer_width': 1, + 'row_support_points': [], + 'bed_count': 1, + 'bed_spacing': 1 + } + for key in defaults: + if key in data: + defaults[key] = data[key] + return defaults @classmethod def from_dict(cls, data: dict[str, Any]) -> Self: - data['first_row_start'] = GeoPoint(lat=data['first_row_start']['lat'], long=data['first_row_start']['long']) - data['first_row_end'] = GeoPoint(lat=data['first_row_end']['lat'], long=data['first_row_end']['long']) + data['first_row_start'] = GeoPoint(lat=data['first_row_start']['lat'], + long=data['first_row_start']['long']) if data.get('first_row_start') else GeoPoint(lat=0, long=0) + data['first_row_end'] = GeoPoint(lat=data['first_row_end']['lat'], + long=data['first_row_end']['long']) if data.get('first_row_end') else GeoPoint(lat=1, long=1) data['row_support_points'] = [rosys.persistence.from_dict( - RowSupportPoint, sp) for sp in data['row_support_points']] if 'row_support_points' in data else [] + RowSupportPoint, sp) for sp in data['row_support_points']] if data.get('row_support_points') else [] field_data = cls(**cls.args_from_dict(data)) + # if id is None, set it to a random uuid + if field_data.id is None: + field_data.id = str(uuid.uuid4()) return field_data def get_buffered_area(self, rows: list[Row], buffer_width: float) -> list[GeoPoint]: diff --git a/field_friend/automations/field_provider.py b/field_friend/automations/field_provider.py index abdb855b..b8af9d76 100644 --- a/field_friend/automations/field_provider.py +++ b/field_friend/automations/field_provider.py @@ -41,10 +41,17 @@ def backup(self) -> dict: } def restore(self, data: dict[str, Any]) -> None: + none_value_warnings = list() fields_data: dict[str, dict] = data.get('fields', {}) for field in list(fields_data.values()): new_field = Field.from_dict(field) self.fields.append(new_field) + # if any value of new_field is None, set none_value_warning to True + if any(value is None for value in vars(new_field).values()): + none_value_warnings.append(new_field.name) + if none_value_warnings: + for warning in none_value_warnings: + rosys.notify(f'Field {warning} had missing values. Replaced by default. Please check the field data.') selected_field_id: str | None = data.get('selected_field') if selected_field_id: self.selected_field = self.get_field(selected_field_id) diff --git a/tests/old_field_provider_persistence.json b/tests/old_field_provider_persistence.json index a1909a8f..f615649b 100644 --- a/tests/old_field_provider_persistence.json +++ b/tests/old_field_provider_persistence.json @@ -11,7 +11,9 @@ "row_spacing": 0.5, "row_count": 10, "outline_buffer_width": 2, - "row_support_points": [] + "row_support_points": [], + "bed_count": 1, + "bed_spacing": 0.5 } } } diff --git a/tests/old_field_provider_persistence_with_errors.json b/tests/old_field_provider_persistence_with_errors.json new file mode 100644 index 00000000..f9dfac2f --- /dev/null +++ b/tests/old_field_provider_persistence_with_errors.json @@ -0,0 +1,19 @@ +{ + "fields": { + "eb24db0f-d48e-4a88-b23a-4833cda55483": { + "id": "eb24db0f-d48e-4a88-b23a-4833cda55483", + "name": "field_1", + "first_row_start": { + "lat": 51.98333789813455, + "long": 7.434242765994318 + }, + "first_row_end": { "lat": 51.98334192260392, "long": 7.434293309874038 }, + "row_spacing": 0.5, + "row_count": 10, + "not_existing_value": true, + "row_support_points": [], + "bed_count": 1, + "bed_spacing": 0.5 + } + } +} diff --git a/tests/test_field_provider.py b/tests/test_field_provider.py index 292c6a31..6841f7b2 100644 --- a/tests/test_field_provider.py +++ b/tests/test_field_provider.py @@ -11,7 +11,7 @@ from field_friend.localization import GeoPoint -def test_loading_from_old_persistence(system: System): +def test_loading_from_persistence(system: System): system.field_provider.restore(json.loads(Path('tests/old_field_provider_persistence.json').read_text())) assert len(system.field_provider.fields) == 1 field = system.field_provider.fields[0] @@ -27,6 +27,25 @@ def test_loading_from_old_persistence(system: System): assert field.first_row_end == GeoPoint(lat=51.98334192260392, long=7.434293309874038) +def test_loading_from_persistence_with_errors(system: System): + system.field_provider.restore(json.loads(Path('tests/old_field_provider_persistence_with_errors.json').read_text())) + assert len(system.field_provider.fields) == 1 + field = system.field_provider.fields[0] + # should set outline_buffer_width to default value because it is missing in the persistence data + assert field.outline_buffer_width == 1 + # should not write the not_existing_value to the field + assert not hasattr(field, 'not_existing_value') + assert field.row_count == 10 + assert field.row_spacing == 0.5 + assert len(field.outline) == 5 + assert len(field.rows) == 10 + assert len(field.row_support_points) == 0 + for row in field.rows: + assert len(row.points) == 2 + assert field.first_row_start == FIELD_FIRST_ROW_START + assert field.first_row_end == GeoPoint(lat=51.98334192260392, long=7.434293309874038) + + def test_field_outline(system: System, field: Field): field = system.field_provider.fields[0] outline = field.outline From e958db3e6322e56b8a41e1a1c49fb6a49c48c6d9 Mon Sep 17 00:00:00 2001 From: Lukas Baecker Date: Mon, 2 Dec 2024 10:39:31 +0100 Subject: [PATCH 2/2] delete field notification --- field_friend/automations/field_provider.py | 7 ------- 1 file changed, 7 deletions(-) diff --git a/field_friend/automations/field_provider.py b/field_friend/automations/field_provider.py index b8af9d76..abdb855b 100644 --- a/field_friend/automations/field_provider.py +++ b/field_friend/automations/field_provider.py @@ -41,17 +41,10 @@ def backup(self) -> dict: } def restore(self, data: dict[str, Any]) -> None: - none_value_warnings = list() fields_data: dict[str, dict] = data.get('fields', {}) for field in list(fields_data.values()): new_field = Field.from_dict(field) self.fields.append(new_field) - # if any value of new_field is None, set none_value_warning to True - if any(value is None for value in vars(new_field).values()): - none_value_warnings.append(new_field.name) - if none_value_warnings: - for warning in none_value_warnings: - rosys.notify(f'Field {warning} had missing values. Replaced by default. Please check the field data.') selected_field_id: str | None = data.get('selected_field') if selected_field_id: self.selected_field = self.get_field(selected_field_id)