Repository: cldf/pycldf

Issue:
Getting the target of a foreign key
I have just tried to update some script to impement what we clarified in the preparation for 1.1, that explicit foreign keys have precedence and do not need to point to ID columns.
The best way I could come up with (commas for tuple unpacking, because all the RHSs of the assigment are length-1 lists)

                    form_column = col_map.cognates.formReference
                    foreign_key, = [
                        key for key in ds["CognateTable"].tableSchema.foreignKeys
                        if key.columnReference == [form_column]
                    ]
                    target, = foreign_key.reference.columnReference

That's quite a chunk for something that should be a core operation. Did I overlook something? If not, could we get something better, such as a property on the column object?

Discussion:
codecov-commenter: # [Codecov](https://codecov.io/gh/cldf/pycldf/pull/134?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Cross-Linguistic+Data+Formats) Report
> Merging [#134](https://codecov.io/gh/cldf/pycldf/pull/134?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Cross-Linguistic+Data+Formats) (edcdc0c) into [master](https://codecov.io/gh/cldf/pycldf/commit/e9da4b86dab0e2517642886fe5c6fce92216f852?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Cross-Linguistic+Data+Formats) (e9da4b8) will **not change** coverage.
> The diff coverage is `100.00%`.

[![Impacted file tree graph](https://codecov.io/gh/cldf/pycldf/pull/134/graphs/tree.svg?width=650&height=150&src=pr&token=j1Q2KE7nTV&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Cross-Linguistic+Data+Formats)](https://codecov.io/gh/cldf/pycldf/pull/134?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Cross-Linguistic+Data+Formats)

```diff
@@            Coverage Diff            @@
##            master      #134   +/-   ##
=========================================
  Coverage   100.00%   100.00%           
=========================================
  Files           25        25           
  Lines         2238      2171   -67     
=========================================
- Hits          2238      2171   -67     
```


| [Impacted Files](https://codecov.io/gh/cldf/pycldf/pull/134?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Cross-Linguistic+Data+Formats) | Coverage Δ | |
|---|---|---|
| [src/pycldf/dataset.py](https://codecov.io/gh/cldf/pycldf/pull/134/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Cross-Linguistic+Data+Formats#diff-c3JjL3B5Y2xkZi9kYXRhc2V0LnB5) | `100.00% <100.00%> (ø)` | |
| [tests/test\_dataset.py](https://codecov.io/gh/cldf/pycldf/pull/134/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Cross-Linguistic+Data+Formats#diff-dGVzdHMvdGVzdF9kYXRhc2V0LnB5) | `100.00% <100.00%> (ø)` | |
| [tests/test\_db.py](https://codecov.io/gh/cldf/pycldf/pull/134/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Cross-Linguistic+Data+Formats#diff-dGVzdHMvdGVzdF9kYi5weQ==) | `100.00% <0.00%> (ø)` | |
| [src/pycldf/orm.py](https://codecov.io/gh/cldf/pycldf/pull/134/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Cross-Linguistic+Data+Formats#diff-c3JjL3B5Y2xkZi9vcm0ucHk=) | `100.00% <0.00%> (ø)` | |
| [tests/conftest.py](https://codecov.io/gh/cldf/pycldf/pull/134/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Cross-Linguistic+Data+Formats#diff-dGVzdHMvY29uZnRlc3QucHk=) | `100.00% <0.00%> (ø)` | |
| [tests/test\_orm.py](https://codecov.io/gh/cldf/pycldf/pull/134/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Cross-Linguistic+Data+Formats#diff-dGVzdHMvdGVzdF9vcm0ucHk=) | `100.00% <0.00%> (ø)` | |
| [tests/test\_util.py](https://codecov.io/gh/cldf/pycldf/pull/134/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Cross-Linguistic+Data+Formats#diff-dGVzdHMvdGVzdF91dGlsLnB5) | `100.00% <0.00%> (ø)` | |
| [src/pycldf/terms.py](https://codecov.io/gh/cldf/pycldf/pull/134/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Cross-Linguistic+Data+Formats#diff-c3JjL3B5Y2xkZi90ZXJtcy5weQ==) | `100.00% <0.00%> (ø)` | |
| [src/pycldf/sources.py](https://codecov.io/gh/cldf/pycldf/pull/134/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Cross-Linguistic+Data+Formats#diff-c3JjL3B5Y2xkZi9zb3VyY2VzLnB5) | `100.00% <0.00%> (ø)` | |
| ... and [3 more](https://codecov.io/gh/cldf/pycldf/pull/134/diff?src=pr&el=tree-more&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Cross-Linguistic+Data+Formats) | |

------

[Continue to review full report at Codecov](https://codecov.io/gh/cldf/pycldf/pull/134?src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Cross-Linguistic+Data+Formats).
> **Legend** - [Click here to learn more](https://docs.codecov.io/docs/codecov-delta?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Cross-Linguistic+Data+Formats)
> `Δ = absolute <relative> (impact)`, `ø = not affected`, `? = missing data`
> Powered by [Codecov](https://codecov.io/gh/cldf/pycldf/pull/134?src=pr&el=footer&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Cross-Linguistic+Data+Formats). Last update [e9da4b8...edcdc0c](https://codecov.io/gh/cldf/pycldf/pull/134?src=pr&el=lastupdated&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Cross-Linguistic+Data+Formats). Read the [comment docs](https://docs.codecov.io/docs/pull-request-comments?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Cross-Linguistic+Data+Formats).


Patch:
diff --git a/src/pycldf/dataset.py b/src/pycldf/dataset.py
index dda51be..1355bfc 100644
--- a/src/pycldf/dataset.py
+++ b/src/pycldf/dataset.py
@@ -626,6 +626,9 @@ class Dataset(object):
         else:
             table, column = item, None
 
+        if isinstance(table, Link):
+            table = table.string
+
         if not isinstance(table, Table):
             uri = term_uri(table, terms=TERMS.by_uri)
             for t in self.tables:
@@ -660,6 +663,22 @@ class Dataset(object):
         except KeyError:
             return default
 
+    def get_foreign_key_reference(self, table, column):
+        """
+        Retrieve the reference of a foreign key constraint for the specified column.
+
+        :param table: Source table, specified by filename, component name or as `Table` instance.
+        :param column: Source column, specified by column name, CLDF term or as `Column` instance.
+        :return: A pair (`Table`, `Column`) specifying the reference column - or `None`.
+        """
+        table = self[table]
+        column = self[table, column]
+
+        for fk in table.tableSchema.foreignKeys:
+            if len(fk.columnReference) == 1 and fk.columnReference[0] == column.name:
+                return self[fk.reference.resource], \
+                    self[fk.reference.resource, fk.reference.columnReference[0]]
+
     def get_row(self, table, id_):
         id_col = self[table, TERMS['id']]
         for row in self[table]:


Test patch:
diff --git a/tests/test_dataset.py b/tests/test_dataset.py
index 87fcbba..c685dd6 100644
--- a/tests/test_dataset.py
+++ b/tests/test_dataset.py
@@ -776,3 +776,13 @@ def test_Dataset_from_url(urlopen):
     assert ds.bibpath == 'http://example.org/ds1.bib'
     assert ds.bibname == 'ds1.bib'
     assert len(ds.sources) == 3
+
+
+def test_Dataset_get_foreign_key_target(tmp_path):
+    ds = StructureDataset.in_dir(tmp_path)
+    ds.add_component('LanguageTable')
+    t, c = ds.get_foreign_key_reference('values.csv', 'Language_ID')
+    assert ds.get_tabletype(t) == 'LanguageTable'
+    assert c.name == 'ID'
+
+    assert ds.get_foreign_key_reference('values.csv', 'Value') is None