Repository: frictionlessdata/frictionless-py

Issue:
Fix error message on yml extension or support it
Hi,
I'm reading https://framework.frictionlessdata.io/docs/guides/getting-started/#usage manual, but I didn't understand what the basic workflow to follow, using the gli.

Imagine I have this CSV:

```
string,number,date
one,1,2021-01-01
two,2,2021-01-02
```

If I run `frictionless describe tmp.csv`, I have

```
---
metadata: tmp.csv
---

encoding: utf-8
format: csv
hashing: md5
name: tmp
path: tmp.csv
profile: tabular-data-resource
schema:
  fields:
    - name: string
      type: string
    - name: number
      type: integer
    - name: date
      type: date
scheme: file
stats:
  bytes: 53
  fields: 3
  hash: 764797fe75bece2a3175303181b48912
  rows: 2
```

If I want to create a Frictionless CSV using the cli, what's the steps do I must run?

Is the one to save the describe output, running `frictionless describe tmp.csv >tmp.resource.yml`?

Once I have it, if I run `frictionless validate tmp.resource.yml --basepath .` I have 

```
---
invalid: tmp.resource.yml
---
==========  ========================================================
code        message
==========  ========================================================
task-error  The task has an error: 'NoneType' object is not iterable
==========  ========================================================
```

Seeing the error I think I have not understood anything and I stop. Then what are the steps i should take to build my first hello world frictionless CSV, using the cli? 

Thank you

Discussion:

Patch:
diff --git a/data/issue305.csv b/data/issue-305.csv
similarity index 100%
rename from data/issue305.csv
rename to data/issue-305.csv
diff --git a/data/issue320.xlsx b/data/issue-320.xlsx
similarity index 100%
rename from data/issue320.xlsx
rename to data/issue-320.xlsx
diff --git a/data/issue493.csv b/data/issue-493.csv
similarity index 100%
rename from data/issue493.csv
rename to data/issue-493.csv
diff --git a/data/issue494.package.json b/data/issue-494.package.json
similarity index 100%
rename from data/issue494.package.json
rename to data/issue-494.package.json
diff --git a/data/issue647.csv.txt b/data/issue-647.csv.txt
similarity index 100%
rename from data/issue647.csv.txt
rename to data/issue-647.csv.txt
diff --git a/data/resource.yaml b/data/resource.yaml
new file mode 100644
index 00000000..76b05a5b
--- /dev/null
+++ b/data/resource.yaml
@@ -0,0 +1,2 @@
+name: name
+path: table.csv
diff --git a/data/resource.yml b/data/resource.yml
new file mode 100644
index 00000000..76b05a5b
--- /dev/null
+++ b/data/resource.yml
@@ -0,0 +1,2 @@
+name: name
+path: table.csv
diff --git a/data/table10.csv b/data/table-10MB.csv
similarity index 100%
rename from data/table10.csv
rename to data/table-10MB.csv
diff --git a/data/table1.csv b/data/table-1MB.csv
similarity index 100%
rename from data/table1.csv
rename to data/table-1MB.csv
diff --git a/data/table5.csv b/data/table-5MB.csv
similarity index 100%
rename from data/table5.csv
rename to data/table-5MB.csv
diff --git a/frictionless/file.py b/frictionless/file.py
index 0c40e43e..d9591e2b 100644
--- a/frictionless/file.py
+++ b/frictionless/file.py
@@ -160,15 +160,15 @@ class File:
                     type = "inquiry"
                 elif data.get("steps") is not None:
                     type = "pipeline"
-            elif not memory and path.endswith((".json", ".yaml")):
+            elif not memory and path.endswith((".json", ".yaml", ".yml")):
                 type = "resource"
-                if path.endswith(("schema.json", "schema.yaml")):
+                if path.endswith(("schema.json", "schema.yaml", "schema.yml")):
                     type = "schema"
-                elif path.endswith(("package.json", "package.yaml")):
+                elif path.endswith(("package.json", "package.yaml", "package.yml")):
                     type = "package"
-                elif path.endswith(("inquiry.json", "inquiry.yaml")):
+                elif path.endswith(("inquiry.json", "inquiry.yaml", "inquiry.yml")):
                     type = "inquiry"
-                elif path.endswith(("pipeline.json", "pipeline.yaml")):
+                elif path.endswith(("pipeline.json", "pipeline.yaml", "pipeline.yml")):
                     type = "pipeline"
 
         # Detect scheme/format/innerpath/compression
diff --git a/frictionless/metadata.py b/frictionless/metadata.py
index b9f7921c..30ef329a 100644
--- a/frictionless/metadata.py
+++ b/frictionless/metadata.py
@@ -201,7 +201,7 @@ class Metadata(helpers.ControlledDict):
                 else:
                     with open(descriptor, encoding="utf-8") as file:
                         content = file.read()
-                if descriptor.endswith(".yaml"):
+                if descriptor.endswith((".yaml", ".yml")):
                     metadata = yaml.safe_load(io.StringIO(content))
                 else:
                     metadata = json.loads(content)


Test patch:
diff --git a/tests/describe/test_resource.py b/tests/describe/test_resource.py
index df230fb1..e44e09a3 100644
--- a/tests/describe/test_resource.py
+++ b/tests/describe/test_resource.py
@@ -137,7 +137,7 @@ def test_describe_resource_values_with_leading_zeros_issue_492():
 
 
 def test_describe_schema_proper_quote_issue_493():
-    resource = describe("data/issue493.csv")
+    resource = describe("data/issue-493.csv")
     assert resource.dialect.quote_char == '"'
     assert len(resource.schema.fields) == 126
 
diff --git a/tests/plugins/test_buffer.py b/tests/plugins/test_buffer.py
index 9c731c7f..4b05cf06 100644
--- a/tests/plugins/test_buffer.py
+++ b/tests/plugins/test_buffer.py
@@ -15,7 +15,7 @@ def test_buffer_loader():
 
 
 def test_buffer_loader_recursion_error_issue_647():
-    with open("data/issue647.csv.txt", "rb") as file:
+    with open("data/issue-647.csv.txt", "rb") as file:
         with Resource(file.read(), format="csv", encoding="iso-8859-1") as resource:
             assert len(resource.read_lists()) == 883
 
diff --git a/tests/test_resource.py b/tests/test_resource.py
index 82e84495..6ab28d4f 100644
--- a/tests/test_resource.py
+++ b/tests/test_resource.py
@@ -43,7 +43,7 @@ def test_resource_from_dict():
     ]
 
 
-def test_resource_from_path():
+def test_resource_from_path_json():
     resource = Resource("data/resource.json")
     assert resource == {"name": "name", "path": "table.csv"}
     assert resource.basepath == "data"
@@ -53,6 +53,26 @@ def test_resource_from_path():
     ]
 
 
+def test_resource_from_path_yaml():
+    resource = Resource("data/resource.yaml")
+    assert resource == {"name": "name", "path": "table.csv"}
+    assert resource.basepath == "data"
+    assert resource.read_rows() == [
+        {"id": 1, "name": "english"},
+        {"id": 2, "name": "中国人"},
+    ]
+
+
+def test_resource_from_path_yml_issue_644():
+    resource = Resource("data/resource.yml")
+    assert resource == {"name": "name", "path": "table.csv"}
+    assert resource.basepath == "data"
+    assert resource.read_rows() == [
+        {"id": 1, "name": "english"},
+        {"id": 2, "name": "中国人"},
+    ]
+
+
 def test_resource_from_path_error_bad_path():
     with pytest.raises(FrictionlessException) as excinfo:
         Resource("data/bad.json")
@@ -2521,7 +2541,7 @@ def test_resource_stats_rows_remote():
 
 def test_resource_stats_rows_significant():
     layout = Layout(header=False)
-    with Resource("data/table1.csv", layout=layout) as resource:
+    with Resource("data/table-1MB.csv", layout=layout) as resource:
         print(resource.read_rows())
         assert resource.stats["rows"] == 10000
 
@@ -2585,7 +2605,7 @@ def test_resource_chardet_raises_remote_issue_305():
 
 
 def test_resource_skip_rows_non_string_cell_issue_320():
-    source = "data/issue320.xlsx"
+    source = "data/issue-320.xlsx"
     dialect = ExcelDialect(fill_merged_cells=True)
     layout = Layout(header_rows=[10, 11, 12])
     with pytest.warns(UserWarning):