Repository: frictionlessdata/frictionless-py

Issue:
Ability to pretty-print metadata in sessions / notebooks
# Overview

Currently, many users use `pprint.pprint` but we can provide some builtin method for it (or use `__repr__` etc). It needs to be thought through


Discussion:

Patch:
diff --git a/frictionless/metadata.py b/frictionless/metadata.py
index cce9ff08..419d13b0 100644
--- a/frictionless/metadata.py
+++ b/frictionless/metadata.py
@@ -11,6 +11,7 @@ from importlib import import_module
 from .exception import FrictionlessException
 from .helpers import cached_property
 from . import helpers
+import pprint as pp
 
 
 # NOTE:
@@ -83,6 +84,10 @@ class Metadata(helpers.ControlledDict):
         if value is not None:
             dict.__setitem__(self, key, value)
 
+    def __repr__(self) -> str:
+        """Returns string representation for metadata."""
+        return pp.pformat(self.to_dict())
+
     # Expand
 
     def expand(self):


Test patch:
diff --git a/tests/test_field.py b/tests/test_field.py
index a6d7977b..c13f282b 100644
--- a/tests/test_field.py
+++ b/tests/test_field.py
@@ -357,3 +357,12 @@ def test_field_set_schema():
 def test_field_set_type():
     field = Field(type="int")
     assert field.type == "int"
+
+
+# Issues
+
+
+def test_field_pprint_1029():
+    field = Field({"name": "name", "type": "string", "constraints": {"maxLength": 2}})
+    expected = """{'constraints': {'maxLength': 2}, 'name': 'name', 'type': 'string'}"""
+    assert repr(field) == expected
diff --git a/tests/test_inquiry.py b/tests/test_inquiry.py
index c2010a62..0fcb34e2 100644
--- a/tests/test_inquiry.py
+++ b/tests/test_inquiry.py
@@ -19,3 +19,20 @@ def test_inquiry_with_task_class():
     )
     report = inquiry.run()
     assert report.valid
+
+
+# Issues
+
+
+def test_inquiry_pprint_1029():
+    inquiry = Inquiry(
+        {
+            "tasks": [
+                {"source": "data/capital-valid.csv"},
+                {"source": "data/capital-invalid.csv"},
+            ]
+        }
+    )
+    expected = """{'tasks': [{'source': 'data/capital-valid.csv'},
+           {'source': 'data/capital-invalid.csv'}]}"""
+    assert repr(inquiry) == expected
diff --git a/tests/test_metadata.py b/tests/test_metadata.py
index bf17bda6..e362940a 100644
--- a/tests/test_metadata.py
+++ b/tests/test_metadata.py
@@ -1,6 +1,5 @@
 from frictionless import Metadata
 
-
 # General
 
 
@@ -12,3 +11,27 @@ def test_descriptor():
 def test_descriptor_from_path():
     metadata = Metadata("data/schema-valid.json")
     assert metadata["primaryKey"] == "id"
+
+
+# Issues
+
+
+def test_metadata_pprint_1029():
+    metadata = Metadata("data/schema-valid.json")
+    expected = """{'fields': [{'constraints': {'required': True},
+             'description': 'The id.',
+             'name': 'id',
+             'title': 'ID',
+             'type': 'integer'},
+            {'constraints': {'required': True},
+             'description': 'The name.',
+             'name': 'name',
+             'title': 'Name',
+             'type': 'string'},
+            {'constraints': {'required': True},
+             'description': 'The age.',
+             'name': 'age',
+             'title': 'Age',
+             'type': 'integer'}],
+ 'primaryKey': 'id'}"""
+    assert repr(metadata) == expected
diff --git a/tests/test_package.py b/tests/test_package.py
index 59d71342..d55a2b68 100644
--- a/tests/test_package.py
+++ b/tests/test_package.py
@@ -1099,3 +1099,11 @@ def test_package_set_trusted():
     assert package.trusted is True
     package.trusted = False
     assert package.trusted is False
+
+
+def test_package_pprint_1029():
+    data = [["id", "name"], ["1", "english"], ["2", "中国人"]]
+    package = Package({"resources": [{"name": "name", "data": data}]})
+    expected = """{'resources': [{'data': [['id', 'name'], ['1', 'english'], ['2', '中国人']],
+                'name': 'name'}]}"""
+    assert repr(package) == expected
diff --git a/tests/test_pipeline.py b/tests/test_pipeline.py
index 30b51af5..3c1a0ed4 100644
--- a/tests/test_pipeline.py
+++ b/tests/test_pipeline.py
@@ -49,3 +49,28 @@ def test_pipeline_package():
     )
     status = pipeline.run()
     assert status.task.target.resource_names == ["data"]
+
+
+# Issues
+
+
+def test_pipeline_pprint_1029():
+    pipeline = Pipeline(
+        {
+            "tasks": [
+                {
+                    "type": "resource",
+                    "source": {"path": "../data/transform.csv"},
+                    "steps": [
+                        {"code": "table-normalize"},
+                        {"code": "table-melt", "fieldName": "name"},
+                    ],
+                }
+            ]
+        }
+    )
+    expected = """{'tasks': [{'source': {'path': '../data/transform.csv'},
+            'steps': [{'code': 'table-normalize'},
+                      {'code': 'table-melt', 'fieldName': 'name'}],
+            'type': 'resource'}]}"""
+    assert repr(pipeline) == expected
diff --git a/tests/test_report.py b/tests/test_report.py
index 493a7db0..f88f7d3e 100644
--- a/tests/test_report.py
+++ b/tests/test_report.py
@@ -100,3 +100,25 @@ def test_report_to_yaml_with_bytes_serialization_issue_836():
     report = validate(source)
     descriptor = report.to_yaml()
     assert "binary" not in descriptor
+
+
+# Issues
+
+
+def test_report_pprint_1029():
+    report = validate(
+        "data/capital-invalid.csv", pick_errors=["duplicate-label"], time=None
+    )
+    expected = """{'errors': [{'code': 'task-error',
+             'description': 'General task-level error.',
+             'message': 'The task has an error: __init__() got an unexpected '
+                        "keyword argument 'time'",
+             'name': 'Task Error',
+             'note': "__init__() got an unexpected keyword argument 'time'",
+             'tags': []}],
+ 'stats': {'errors': 1, 'tasks': 0},
+ 'tasks': [],
+ 'time': 0.0,
+ 'valid': False,
+ 'version': '4.29.0'}"""
+    assert repr(report) == expected
diff --git a/tests/test_resource.py b/tests/test_resource.py
index da61276b..6d91653d 100644
--- a/tests/test_resource.py
+++ b/tests/test_resource.py
@@ -2748,3 +2748,17 @@ def test_resource_set_package():
     test_package_2 = Package()
     resource.package = test_package_2
     assert resource.package == test_package_2
+
+
+def test_resource_pprint_1029():
+    resource = Resource(
+        name="resource",
+        title="My Resource",
+        description="My Resource for the Guide",
+        path="data/table.csv",
+    )
+    expected = """{'description': 'My Resource for the Guide',
+ 'name': 'resource',
+ 'path': 'data/table.csv',
+ 'title': 'My Resource'}"""
+    assert repr(resource) == expected
diff --git a/tests/test_schema.py b/tests/test_schema.py
index 9b9e7f09..1e53b25d 100644
--- a/tests/test_schema.py
+++ b/tests/test_schema.py
@@ -467,3 +467,18 @@ def test_schema_not_supported_type_issue_goodatbles_304():
     schema = Schema({"fields": [{"name": "name"}, {"name": "age", "type": "bad"}]})
     assert schema.metadata_valid is False
     assert schema.fields[1] == {"name": "age", "type": "bad"}
+
+
+def test_schema_pprint_1029():
+    descriptor = {
+        "fields": [
+            {"name": "test_1", "type": "string", "format": "default"},
+            {"name": "test_2", "type": "string", "format": "default"},
+            {"name": "test_3", "type": "string", "format": "default"},
+        ]
+    }
+    schema = Schema(descriptor)
+    expected = """{'fields': [{'format': 'default', 'name': 'test_1', 'type': 'string'},
+            {'format': 'default', 'name': 'test_2', 'type': 'string'},
+            {'format': 'default', 'name': 'test_3', 'type': 'string'}]}"""
+    assert repr(schema) == expected