Repository: frictionlessdata/frictionless-py

Issue:
`frictionless validate` no longer accepts list of resources
According to the specs,  resource path should accept a list of files:

> To support this use case the path property MAY be an array of strings rather
than a single string:

Since frictionless v4.40.0, this causes an error:

```
TypeError: stat: path should be string, bytes, os.PathLike or integer, not list
```

I make extensive use of list of paths, so I hope this can be fixed. 😄 

## Test files

datapackage.json:
```
{
  "profile": "tabular-data-package",
  "resources": [
    {
      "name": "measurements",
      "path": [
        "https://github.com/frictionlessdata/frictionless-py/files/8941635/file_1.csv",
        "https://github.com/frictionlessdata/frictionless-py/files/8941634/file_2.csv"
      ],
      "profile": "tabular-data-resource",
      "schema": {
        "fields": [
          {
            "name": "location",
            "type": "string"
          },
          {
            "name": "measurement",
            "type": "number"
          }
        ]
      }
    }
  ]
}
```
[file_1.csv](https://github.com/frictionlessdata/frictionless-py/files/8941635/file_1.csv)
[file_2.csv](https://github.com/frictionlessdata/frictionless-py/files/8941634/file_2.csv)


Discussion:
shashigharti: @roll it is ready for the review. I also made changes to display names of all files (partial files)
https://github.com/frictionlessdata/frictionless-py/pull/1142/commits/c09edfb9dfb5915b983e8fd876b85fa37c9a47e7#diff-d5276d48043871fbf626becc36110670e1cb88ef2c1c96751cb2952a0f30b7adR410

Patch:
diff --git a/data/multipart-zipped.package.json b/data/multipart-zipped.package.json
new file mode 100644
index 00000000..c2297a2c
--- /dev/null
+++ b/data/multipart-zipped.package.json
@@ -0,0 +1,11 @@
+{
+  "profile": "tabular-data-package",
+  "resources": [
+    {
+      "name": "measurements",
+      "path": ["chunk1.csv.zip", "chunk2.csv.zip"],
+      "profile": "tabular-data-resource",
+      "schema": "resource-schema.json"
+    }
+  ]
+}
diff --git a/data/multipart.package.json b/data/multipart.package.json
new file mode 100644
index 00000000..231b64d0
--- /dev/null
+++ b/data/multipart.package.json
@@ -0,0 +1,11 @@
+{
+  "profile": "tabular-data-package",
+  "resources": [
+    {
+      "name": "measurements",
+      "path": ["chunk1.csv", "chunk2.csv"],
+      "profile": "tabular-data-resource",
+      "schema": "resource-schema.json"
+    }
+  ]
+}
diff --git a/frictionless/report/report.py b/frictionless/report/report.py
index 07157d14..4996a538 100644
--- a/frictionless/report/report.py
+++ b/frictionless/report/report.py
@@ -1,4 +1,3 @@
-import os
 import functools
 import textwrap
 from copy import deepcopy
@@ -408,15 +407,10 @@ class ReportTask(Metadata):
         """
         source = self.resource.path or self.resource.name
         if source and isinstance(source, list):
-            source = source[0]
+            source = ",".join(source)
         # For zipped resources append file name
         if self.resource.innerpath:
             source = f"{source} => {self.resource.innerpath}"
-        file_path = (
-            os.path.join(self.resource.basepath, source)
-            if self.resource.basepath
-            else source
-        )
         # Prepare error lists and last row checked(in case of partial validation)
         error_list = {}
         for error in self.errors:
@@ -427,10 +421,9 @@ class ReportTask(Metadata):
             if self.partial:
                 last_row_checked = error.get("rowPosition", "")
         rows_checked = last_row_checked if self.partial else None
-        file_size = "N/A"
         unit = None
-        if os.path.exists(file_path):
-            file_size = os.path.getsize(file_path)
+        file_size = self.resource.stats["bytes"]
+        if file_size > 0:
             unit = helpers.format_bytes(file_size)
         not_found_text = ""
         if not unit:
@@ -438,7 +431,7 @@ class ReportTask(Metadata):
                 not_found_text = "(Not Found)"
         content = [
             [f"File name {not_found_text}", source],
-            [f"File size { f'({unit})' if unit else '' }", file_size],
+            [f"File size { f'({unit})' if unit else '' }", file_size or "N/A"],
             ["Total Time Taken (sec)", self.time],
         ]
         if rows_checked:


Test patch:
diff --git a/tests/program/test_validate.py b/tests/program/test_validate.py
index 00480368..dfbd7e02 100644
--- a/tests/program/test_validate.py
+++ b/tests/program/test_validate.py
@@ -270,6 +270,18 @@ def test_program_validate_single_invalid_resource_221():
     assert result.stdout.count("invalid: data/datapackage.json")
 
 
+def test_program_validate_multipart_resource_1140():
+    result = runner.invoke(program, "validate data/multipart.package.json")
+    assert result.exit_code == 0
+    assert result.stdout.count("chunk1.csv,chunk2.csv")
+
+
+def test_program_validate_multipart_zipped_resource_1140():
+    result = runner.invoke(program, "validate data/multipart-zipped.package.json")
+    assert result.exit_code == 0
+    assert result.stdout.count("chunk1.csv.zip,chunk2.csv.zip")
+
+
 # Helpers
 
 
diff --git a/tests/report/test_reporttask.py b/tests/report/test_reporttask.py
index bd3e2f27..c23cb43b 100644
--- a/tests/report/test_reporttask.py
+++ b/tests/report/test_reporttask.py
@@ -47,11 +47,7 @@ def test_report_reporttask_summary_filenotfound():
 def test_report_reporttask_summary_zippedfile():
     report = validate("data/table.csv.zip")
     output = report.tasks[0].to_summary()
-    assert (
-        output.count("File name              | data/table.csv.zip => table.csv")
-        and output.count("File size              | N/A")
-        and output.count("Total Time Taken (sec) |")
-    )
+    assert output.count("data/table.csv.zip => table.csv") and output.count("198")
 
 
 def test_report_reporttask_summary_lastrowchecked():