Repository: frictionlessdata/frictionless-py

Issue:
Validate fields[].example for Schema
# Overview

Related to https://github.com/frictionlessdata/specs/pull/753

If a field has an `example` key, make sure that the `example` is valid according to the field's type and constraints.


Discussion:
AntoineAugusti: Not sure what I can do in regards to the ` project-assign` CI, let me know
AntoineAugusti: @roll Thanks, I've done the suggested updates

Patch:
diff --git a/frictionless/field.py b/frictionless/field.py
index 2d812d86..085df79d 100644
--- a/frictionless/field.py
+++ b/frictionless/field.py
@@ -58,6 +58,7 @@ class Field(Metadata):
         float_number=None,
         decimal_char=None,
         group_char=None,
+        example=None,
         # Extra
         schema=None,
     ):
@@ -77,6 +78,7 @@ class Field(Metadata):
         self.setinitial("decimalChar", decimal_char)
         self.setinitial("groupChar", group_char)
         self.setinitial("rdfType", rdf_type)
+        self.setinitial("example", example)
         self.__schema = schema
         self.__type = None
         super().__init__(descriptor)
@@ -286,6 +288,14 @@ class Field(Metadata):
         """
         return self.get("groupChar", settings.DEFAULT_GROUP_CHAR)
 
+    @Metadata.property
+    def example(self):
+        """
+        Returns:
+            any: example value
+        """
+        return self.get("example", None)
+
     # Expand
 
     def expand(self):
diff --git a/frictionless/schema.py b/frictionless/schema.py
index 89bda78a..d409a476 100644
--- a/frictionless/schema.py
+++ b/frictionless/schema.py
@@ -302,6 +302,13 @@ class Schema(Metadata):
             if field.builtin:
                 yield from field.metadata_errors
 
+        # Examples
+        for field in [f for f in self.fields if "example" in field]:
+            _, notes = field.read_cell(field.example)
+            if notes is not None:
+                note = 'example value for field "%s" is not valid' % field.name
+                yield errors.SchemaError(note=note)
+
         # Primary Key
         for name in self.primary_key:
             if name not in self.field_names:


Test patch:
diff --git a/tests/test_field.py b/tests/test_field.py
index 946f42bf..ba8389bf 100644
--- a/tests/test_field.py
+++ b/tests/test_field.py
@@ -321,6 +321,12 @@ def test_field_read_cell_multiple_constraints():
     assert read("") == (None, None)
 
 
+@pytest.mark.parametrize("example_value", [(None), (42), ("foo")])
+def test_field_with_example_set(example_value):
+    field = Field({"name": "name", "type": "string", "example": example_value})
+    assert field.example == example_value
+
+
 # Import/Export
 
 
diff --git a/tests/test_schema.py b/tests/test_schema.py
index 9d527d98..9b9e7f09 100644
--- a/tests/test_schema.py
+++ b/tests/test_schema.py
@@ -255,6 +255,37 @@ def test_schema_metadata_error_message():
     assert "is not valid under any of the given schema" in note
 
 
+def test_schema_valid_examples():
+    schema = Schema(
+        {
+            "fields": [
+                {"name": "name", "type": "string", "example": "John"},
+                {"name": "age", "type": "integer", "example": 42},
+            ]
+        }
+    )
+    assert schema.get_field("name").example == "John"
+    assert len(schema.metadata_errors) == 0
+
+
+def test_schema_invalid_example():
+    schema = Schema(
+        {
+            "fields": [
+                {
+                    "name": "name",
+                    "type": "string",
+                    "example": None,
+                    "constraints": {"required": True},
+                }
+            ]
+        }
+    )
+    note = schema.metadata_errors[0]["note"]
+    assert len(schema.metadata_errors) == 1
+    assert 'example value for field "name" is not valid' == note
+
+
 @pytest.mark.parametrize("create_descriptor", [(False,), (True,)])
 def test_schema_standard_specs_properties(create_descriptor):
     options = dict(