Repository: cldf/csvw

Issue:
Support for remote table schemas?
CSV on the Web officially specifies that table schemas can be metadata files remotely located on the Web via a URL, see definition from the [spec](https://www.w3.org/TR/tabular-metadata/):

> `tableSchema`: An object property that provides a single schema description as described in section 5.5 Schemas, used as the default for all the tables in the group. This may be provided as an embedded object within the JSON metadata or as a URL reference to a separate JSON object that is a schema description.

Currently, this library doesn't offer this feature.

I can see two possible solutions for now:
1) Implement referencing and loading of remote schemas directly within this library.
2) Make a screening step and inject any remotely located table schemas into the JSON before feeding it to this library.

Naturally, I would prefer option 1. I'm aware there is the caveat that then validation cannot happen offline. One could imagine caching, though. What do you think on this? Can you imagine this feature being added in principle?

Discussion:

Patch:
diff --git a/src/csvw/metadata.py b/src/csvw/metadata.py
index 6f17514..3467529 100644
--- a/src/csvw/metadata.py
+++ b/src/csvw/metadata.py
@@ -16,6 +16,7 @@ import warnings
 import itertools
 import collections
 from urllib.parse import urljoin
+from urllib.request import urlopen
 
 from ._compat import pathlib, py3_unicode_to_str, json_open
 
@@ -489,6 +490,13 @@ class Schema(Description):
             col._parent = self
             col._number = i + 1
 
+    @classmethod
+    def fromvalue(cls, v):
+        if isinstance(v, str):
+            # The schema is referenced with a URL
+            v = json.loads(urlopen(v).read().decode('utf8'))
+        return cls(**cls.partition_properties(v))
+
     @property
     def columndict(self):
         return {c.header: c for c in self.columns}


Test patch:
diff --git a/tests/test_metadata.py b/tests/test_metadata.py
index b10f8ab..febcb5a 100644
--- a/tests/test_metadata.py
+++ b/tests/test_metadata.py
@@ -1,3 +1,4 @@
+import io
 import json
 import shutil
 import collections
@@ -712,3 +713,30 @@ AF,9799379"""}
                 data['country_slice.csv'].replace('AF;AD', 'AF;AX'), encoding='utf-8')
             with pytest.raises(ValueError):
                 tg.check_referential_integrity()
+
+    def test_remote_schema(self, mocker, tmpdir):
+        schema = """
+        {
+            "columns": [
+                {"name": "countryCode", "datatype": "string"},
+                {"name": "name", "datatype": "string"}
+            ]
+        }
+        """
+        mocker.patch(
+            'csvw.metadata.urlopen', mocker.Mock(return_value=io.BytesIO(schema.encode('utf8'))))
+        tg = self._make_tablegroup(
+            tmpdir,
+            metadata="""{
+  "@context": "http://www.w3.org/ns/csvw",
+  "tables": [{
+    "url": "countries.csv",
+    "tableSchema": "url"
+  }]
+}""")
+        assert len(tg.tables[0].tableSchema.columns) == 2
+
+        # The remote content has been inlined:
+        out = pathlib.Path(str(tmpdir)) / 'md.json'
+        tg.to_file(out)
+        assert 'countryCode' in out.read_text(encoding='utf8')