diff --git a/README.md b/README.md
index 4514cc7..1bbedfe 100644
--- a/README.md
+++ b/README.md
@@ -33,6 +33,7 @@ of your data (e.g. all rows have the same number of columns), and its contents
     - [Advanced checks](#advanced-checks)
       - [blacklisted-value](#blacklisted-value)
       - [deviated-value](#deviated-value)
+      - [foreign-key](#foreign-key)
       - [sequential-value](#sequential-value)
       - [truncated-value](#truncated-value)
       - [custom-constraint](#custom-constraint)
@@ -146,6 +147,7 @@ Lastly, if the order of the fields in the data is different than in your schema,
 | --- | --- |
 | [blacklisted-value](#blacklisted-value) | Ensure there are no cells with the blacklisted values. |
 | [deviated-value](#deviated-value) | Ensure numbers are within a number of standard deviations from the average. |
+| [foreign-key](#foreign-key) | Ensure foreign keys are valid within a data package |
 | [sequential-value](#sequential-value) | Ensure numbers are sequential. |
 | [truncated-value](#truncated-value) | Detect values that were potentially truncated. |
 | [custom-constraint](#custom-constraint) | Defines a constraint based on the values of other columns (e.g. `value * quantity == total`). |
@@ -217,6 +219,107 @@ report = validate('data.csv', checks=[
 # error on row 10 with code "deviated-value"
 ```
 
+#### foreign-key
+
+> We support here relative paths. It MUST be used only for trusted data sources.
+
+This check validate foreign keys within a data package. Consider we have a data package defined below:
+
+```python
+DESCRIPTOR = {
+  'resources': [
+    {
+      'name': 'cities',
+      'data': [
+        ['id', 'name', 'next_id'],
+        [1, 'london', 2],
+        [2, 'paris', 3],
+        [3, 'rome', 4],
+        # [4, 'rio', None],
+      ],
+      'schema': {
+        'fields': [
+          {'name': 'id', 'type': 'integer'},
+          {'name': 'name', 'type': 'string'},
+          {'name': 'next_id', 'type': 'integer'},
+        ],
+        'foreignKeys': [
+          {
+            'fields': 'next_id',
+            'reference': {'resource': '', 'fields': 'id'},
+          },
+          {
+            'fields': 'id',
+            'reference': {'resource': 'people', 'fields': 'label'},
+          },
+        ],
+      },
+    }, {
+      'name': 'people',
+      'data': [
+        ['label', 'population'],
+        [1, 8],
+        [2, 2],
+        # [3, 3],
+        # [4, 6],
+      ],
+    },
+  ],
+}
+```
+
+Running `goodtables` on it will raise a few `foreign-key` errors because we have commented some rows in the data package's data:
+
+```python
+report = validate(DESCRIPTOR, checks=['structure', 'schema', 'foreign-key'])
+print(report)
+```
+
+```
+{'error-count': 2,
+ 'preset': 'datapackage',
+ 'table-count': 2,
+ 'tables': [{'datapackage': '...',
+             'error-count': 2,
+             'errors': [{'code': 'foreign-key',
+                         'message': 'Foreign key "[\'next_id\']" violation in '
+                                    'row 4',
+                         'message-data': {'fields': ['next_id']},
+                         'row-number': 4},
+                        {'code': 'foreign-key',
+                         'message': 'Foreign key "[\'id\']" violation in row 4',
+                         'message-data': {'fields': ['id']},
+                         'row-number': 4}],
+             'format': 'inline',
+             'headers': ['id', 'name', 'next_id'],
+             'resource-name': 'cities',
+             'row-count': 4,
+             'schema': 'table-schema',
+             'source': 'inline',
+             'time': 0.031,
+             'valid': False},
+            {'datapackage': '...',
+             'error-count': 0,
+             'errors': [],
+             'format': 'inline',
+             'headers': ['label', 'population'],
+             'resource-name': 'people',
+             'row-count': 3,
+             'source': 'inline',
+             'time': 0.038,
+             'valid': True}],
+ 'time': 0.117,
+ 'valid': False,
+ 'warnings': []}
+```
+
+It experimetally supports external resource checks, for example, for a `foreignKey` definition like these:
+
+```json
+{"package": "../people/datapackage.json", "resource": "people", "fields": "label"}
+{"package": "http:/example.com/datapackage.json", "resource": "people", "fields": "label"}
+```
+
 #### sequential-value
 
 This checks is for pretty common case when a column should have integers that sequentially increment.  It accepts following options:
diff --git a/data/datapackages_linked/cities/cities.csv b/data/datapackages_linked/cities/cities.csv
new file mode 100644
index 0000000..d896cd2
--- /dev/null
+++ b/data/datapackages_linked/cities/cities.csv
@@ -0,0 +1,5 @@
+id,name,next_id
+1,london,2
+2,paris,3
+3,rome,4
+4,rio,
diff --git a/data/datapackages_linked/cities/datapackage.json b/data/datapackages_linked/cities/datapackage.json
new file mode 100644
index 0000000..fc32533
--- /dev/null
+++ b/data/datapackages_linked/cities/datapackage.json
@@ -0,0 +1,39 @@
+{
+  "profile": "tabular-data-package",
+  "resources": [
+    {
+      "path": "cities.csv",
+      "profile": "tabular-data-resource",
+      "name": "cities",
+      "schema": {
+        "fields": [
+          {
+            "name": "id",
+            "type": "integer"
+          },
+          {
+            "name": "name",
+            "type": "string"
+          },
+          {
+            "name": "next_id",
+            "type": "integer"
+          }
+        ],
+        "foreignKeys": [
+          {
+            "fields": "next_id",
+            "reference": {"resource": "", "fields": "id"}
+          },
+          {
+            "fields": "id",
+            "reference": {"package": "../people/datapackage.json", "resource": "people", "fields": "label"}
+          }
+        ],
+        "missingValues": [
+          ""
+        ]
+      }
+    }
+  ]
+}
diff --git a/data/datapackages_linked/people/datapackage.json b/data/datapackages_linked/people/datapackage.json
new file mode 100644
index 0000000..e0d69a9
--- /dev/null
+++ b/data/datapackages_linked/people/datapackage.json
@@ -0,0 +1,24 @@
+{
+  "profile": "tabular-data-package",
+  "resources": [
+    {
+      "path": "people.csv",
+      "name": "people",
+      "schema": {
+        "fields": [
+          {
+            "name": "label",
+            "type": "integer"
+          },
+          {
+            "name": "population",
+            "type": "string"
+          }
+        ],
+        "missingValues": [
+          ""
+        ]
+      }
+    }
+  ]
+}
diff --git a/data/datapackages_linked/people/people.csv b/data/datapackages_linked/people/people.csv
new file mode 100644
index 0000000..d0590da
--- /dev/null
+++ b/data/datapackages_linked/people/people.csv
@@ -0,0 +1,5 @@
+label,population
+1,8
+2,2
+3,3
+4,6
diff --git a/data/datapackages_linked_errors/cities/cities.csv b/data/datapackages_linked_errors/cities/cities.csv
new file mode 100644
index 0000000..cc4fe27
--- /dev/null
+++ b/data/datapackages_linked_errors/cities/cities.csv
@@ -0,0 +1,4 @@
+id,name,next_id
+1,london,2
+2,paris,3
+3,rome,4
diff --git a/data/datapackages_linked_errors/cities/datapackage.json b/data/datapackages_linked_errors/cities/datapackage.json
new file mode 100644
index 0000000..fc32533
--- /dev/null
+++ b/data/datapackages_linked_errors/cities/datapackage.json
@@ -0,0 +1,39 @@
+{
+  "profile": "tabular-data-package",
+  "resources": [
+    {
+      "path": "cities.csv",
+      "profile": "tabular-data-resource",
+      "name": "cities",
+      "schema": {
+        "fields": [
+          {
+            "name": "id",
+            "type": "integer"
+          },
+          {
+            "name": "name",
+            "type": "string"
+          },
+          {
+            "name": "next_id",
+            "type": "integer"
+          }
+        ],
+        "foreignKeys": [
+          {
+            "fields": "next_id",
+            "reference": {"resource": "", "fields": "id"}
+          },
+          {
+            "fields": "id",
+            "reference": {"package": "../people/datapackage.json", "resource": "people", "fields": "label"}
+          }
+        ],
+        "missingValues": [
+          ""
+        ]
+      }
+    }
+  ]
+}
diff --git a/data/datapackages_linked_errors/people/datapackage.json b/data/datapackages_linked_errors/people/datapackage.json
new file mode 100644
index 0000000..e0d69a9
--- /dev/null
+++ b/data/datapackages_linked_errors/people/datapackage.json
@@ -0,0 +1,24 @@
+{
+  "profile": "tabular-data-package",
+  "resources": [
+    {
+      "path": "people.csv",
+      "name": "people",
+      "schema": {
+        "fields": [
+          {
+            "name": "label",
+            "type": "integer"
+          },
+          {
+            "name": "population",
+            "type": "string"
+          }
+        ],
+        "missingValues": [
+          ""
+        ]
+      }
+    }
+  ]
+}
diff --git a/data/datapackages_linked_errors/people/people.csv b/data/datapackages_linked_errors/people/people.csv
new file mode 100644
index 0000000..2f4b85f
--- /dev/null
+++ b/data/datapackages_linked_errors/people/people.csv
@@ -0,0 +1,3 @@
+label,population
+1,8
+2,2
diff --git a/goodtables/cells.py b/goodtables/cells.py
index 6227d71..74763fb 100644
--- a/goodtables/cells.py
+++ b/goodtables/cells.py
@@ -35,8 +35,6 @@ def create_cells(headers, schema_fields, values=None, row_number=None):
             field = None
         if value == fillvalue:
             value = None
-        elif value is None:
-            value = ''
 
         cell = create_cell(header, value, field, column_number, row_number)
         cells.append(cell)
diff --git a/goodtables/checks/required_constraint.py b/goodtables/checks/required_constraint.py
index 1ef90db..a448a2e 100644
--- a/goodtables/checks/required_constraint.py
+++ b/goodtables/checks/required_constraint.py
@@ -29,7 +29,7 @@ def required_constraint(cells):
         if field.required or field.descriptor.get('primaryKey'):
             # TODO: remove this hack after:
             # https://github.com/frictionlessdata/tableschema-py/issues/244
-            if value in field._Field__missing_values:
+            if value is None or value in field._Field__missing_values:
                 valid = False
 
         # Skip if valid
diff --git a/goodtables/checks/type_or_format_error.py b/goodtables/checks/type_or_format_error.py
index b1194d1..f1d1d4a 100644
--- a/goodtables/checks/type_or_format_error.py
+++ b/goodtables/checks/type_or_format_error.py
@@ -28,7 +28,7 @@ def type_or_format_error(cells):
         # Cast value
         try:
             valid = True
-            field.cast_value(value, constraints=False)
+            cell['value'] = field.cast_value(value, constraints=False)
         except tableschema.exceptions.TableSchemaException:
             valid = False
 
diff --git a/goodtables/config.py b/goodtables/config.py
index fd3a57c..8c2ec48 100644
--- a/goodtables/config.py
+++ b/goodtables/config.py
@@ -44,6 +44,7 @@ CHECKS = [
     # Contrib
     'goodtables.contrib.checks.blacklisted_value',
     'goodtables.contrib.checks.deviated_value',
+    'goodtables.contrib.checks.foreign_key',
     'goodtables.contrib.checks.sequential_value',
     'goodtables.contrib.checks.truncated_value',
     'goodtables.contrib.checks.custom_constraint',
diff --git a/goodtables/contrib/checks/foreign_key.py b/goodtables/contrib/checks/foreign_key.py
new file mode 100644
index 0000000..745a06c
--- /dev/null
+++ b/goodtables/contrib/checks/foreign_key.py
@@ -0,0 +1,168 @@
+# -*- coding: utf-8 -*-
+from __future__ import division
+from __future__ import print_function
+from __future__ import absolute_import
+from __future__ import unicode_literals
+
+import json
+from copy import deepcopy
+from collections import defaultdict
+from datapackage import Package
+from ...registry import check
+from ...error import Error
+
+
+# Module API
+
+@check('foreign-key', type='custom', context='body')
+class ForeignKey(object):
+
+    # Public
+
+    def __init__(self, **options):
+        self.__package = None
+        self.__schema = None
+        self.__relations = None
+        self.__code = 'foreign-key'
+
+    def prepare(self, stream, schema, extra):
+
+        # Prepare package
+        if 'datapackage' not in extra or 'resource-name' not in extra:
+            return False
+        descriptor = extra['datapackage']
+        if descriptor.strip().startswith('{'):
+            descriptor = json.loads(descriptor)
+        self.__package = Package(descriptor)
+
+        # Prepare schema
+        if not schema:
+            return False
+        if not schema.foreign_keys:
+            return False
+        self.__schema = schema
+
+        # Prepare foreign keys values
+        relations = _get_relations(
+            self.__package, self.__schema,
+            current_resource_name=extra['resource-name'])
+        self.__foreign_keys_values = _get_foreign_keys_values(
+            self.__schema, relations)
+
+        return True
+
+    def check_row(self, cells):
+        row_number = cells[0]['row-number']
+
+        # Prepare keyed_row
+        keyed_row = {}
+        for cell in cells:
+            if cell.get('field'):
+                keyed_row[cell.get('field').name] = cell.get('value')
+
+        # Resolve relations
+        errors = []
+        for foreign_key in self.__schema.foreign_keys:
+            success = _resolve_relations(
+                deepcopy(keyed_row), self.__foreign_keys_values, foreign_key)
+            if success is None:
+                message = 'Foreign key "{fields}" violation in row {row_number}'
+                message_substitutions = {'fields': foreign_key['fields']}
+                errors.append(Error(
+                    self.__code,
+                    row_number=row_number,
+                    message=message,
+                    message_substitutions=message_substitutions,
+                ))
+
+        return errors
+
+
+# Internal
+
+def _get_relations(package, schema, current_resource_name=None):
+    # It's based on the following code:
+    # https://github.com/frictionlessdata/datapackage-py/blob/master/datapackage/resource.py#L393
+
+    # Prepare relations
+    relations = {}
+    for fk in schema.foreign_keys:
+        resource_name = fk['reference'].get('resource')
+        package_name = fk['reference'].get('package')
+        resource = None
+
+        # Self-referenced resource
+        if not resource_name:
+            for item in package.resources:
+                if item.name == current_resource_name:
+                    resource = item
+
+        # Internal resource
+        elif not package_name:
+            resource = package.get_resource(resource_name)
+
+        # External resource (experimental)
+        # For now, we rely on uniqueness of resource names and support relative paths
+        else:
+            descriptor = package_name
+            if not descriptor.startswith('http'):
+                descriptor = '/'.join([package.base_path, package_name])
+            package = Package(descriptor)
+            resource = package.get_resource(resource_name)
+
+        # Add to relations (can be None)
+        relations[resource_name] = resource
+        if resource and resource.tabular:
+            relations[resource_name] = resource.read(keyed=True)
+
+    return relations
+
+
+def _get_foreign_keys_values(schema, relations):
+    # It's based on the following code:
+    # https://github.com/frictionlessdata/tableschema-py/blob/master/tableschema/table.py#L218
+
+    # we dont need to load the complete reference table to test relations
+    # we can lower payload AND optimize testing foreign keys
+    # by preparing the right index based on the foreign key definition
+    # foreign_keys are sets of tuples of all possible values in the foreign table
+    # foreign keys =
+    # [reference] [foreign_keys tuple] = { (foreign_keys_values, ) : one_keyedrow, ... }
+    foreign_keys = defaultdict(dict)
+    if schema:
+        for fk in schema.foreign_keys:
+            # load relation data
+            relation = fk['reference']['resource']
+
+            # create a set of foreign keys
+            # to optimize we prepare index of existing values
+            # this index should use reference + foreign_keys as key
+            # cause many foreign keys may use the same reference
+            foreign_keys[relation][tuple(fk['reference']['fields'])] = {}
+            for row in (relations[relation] or []):
+                key = tuple([row[foreign_field] for foreign_field in fk['reference']['fields']])
+                # here we should chose to pick the first or nth row which match
+                # previous implementation picked the first, so be it
+                if key not in foreign_keys[relation][tuple(fk['reference']['fields'])]:
+                    foreign_keys[relation][tuple(fk['reference']['fields'])][key] = row
+    return foreign_keys
+
+
+def _resolve_relations(keyed_row, foreign_keys_values, foreign_key):
+    # It's based on the following code:
+    # https://github.com/frictionlessdata/tableschema-py/blob/master/tableschema/table.py#L228
+
+    # local values of the FK
+    local_values = tuple(keyed_row[f] for f in foreign_key['fields'])
+    if len([l for l in local_values if l]) > 0:
+        # test existence into the foreign
+        relation = foreign_key['reference']['resource']
+        keys = tuple(foreign_key['reference']['fields'])
+        foreign_values = foreign_keys_values[relation][keys]
+        if local_values in foreign_values:
+            return foreign_values[local_values]
+        else:
+            return None
+    else:
+        # empty values for all keys, return original values
+        return keyed_row
diff --git a/goodtables/inspector.py b/goodtables/inspector.py
index 3591b73..e17e464 100644
--- a/goodtables/inspector.py
+++ b/goodtables/inspector.py
@@ -191,6 +191,18 @@ class Inspector(object):
                     error = _compose_error_from_schema_error(error)
                     errors.append(error)
 
+        # Prepare checks
+        # This is an experimental hook
+        for check in checks:
+            prepare_func = getattr(check['func'], 'prepare', None)
+            if not prepare_func:
+                continue
+            success = prepare_func(stream, schema, extra)
+            if not success:
+                checks.remove(check)
+        if not checks:
+            fatal_error = True
+
         # Head checks
         if not fatal_error:
             # Prepare cells
diff --git a/goodtables/presets/datapackage.py b/goodtables/presets/datapackage.py
index dc18fbe..cfc994b 100644
--- a/goodtables/presets/datapackage.py
+++ b/goodtables/presets/datapackage.py
@@ -4,6 +4,7 @@ from __future__ import print_function
 from __future__ import absolute_import
 from __future__ import unicode_literals
 
+import json
 from tabulator import Stream
 from functools import partial
 from datapackage import Package, exceptions
@@ -34,7 +35,8 @@ def datapackage(source, **options):
                     'stream': Stream(partial(_iter_resource_rows, resource), headers=1),
                     'schema': resource.schema,
                     'extra': {
-                        'datapackage': str(source),
+                        'datapackage': json.dumps(source) if isinstance(source, dict) else source,
+                        'resource-name': resource.name,
                     },
                 })
 
diff --git a/pylama.ini b/pylama.ini
index aefae4b..ce8b52a 100644
--- a/pylama.ini
+++ b/pylama.ini
@@ -3,7 +3,7 @@ linters = pyflakes,mccabe,pep8
 ignore = E128,E301
 
 [pylama:pep8]
-max_line_length = 90
+max_line_length = 100
 
 [pylama:mccabe]
 complexity = 48
diff --git a/setup.py b/setup.py
index 01248de..2604157 100644
--- a/setup.py
+++ b/setup.py
@@ -28,7 +28,7 @@ INSTALL_REQUIRES = [
     'simpleeval>=0.9',
     'statistics>=1.0',
     'tabulator>=1.20',
-    'tableschema>=1.0.3',
+    'tableschema>=1.7',
     'datapackage>=1.2',
 ]
 INSTALL_FORMAT_ODS_REQUIRES = [

Add support for foreign keys?
Below is my datapackage.json

`{ "name": "testing", "resources": [ { "name": "data", "path": "data.csv", "schema": { "fields": [ { "name": "id", "type": "string", "constraints": { "required": true } }, { "name": "name", "type": "string" }, { "name": "description", "type": "string" }, { "name": "amount", "type": "number" } ], "primaryKey": ["id"] } }, { "name": "data2", "path": "data2.csv", "schema": { "fields": [ { "name": "id" }, { "type": "string", "name": "comment" } ], "foreignKeys": [ { "fields": ["id"], "reference": { "resource": "data", "fields": ["id"] } } ] } } ] }`

**data.csv**
id,name,description,amount
A999,Taxes,Taxes we collect,10000.5
A5032,Parking Fees,Parking fees we collect,2000.5

**data2.csv**

id,comment
A3001,comment1
A3001,comment2
A5032,comment3

As you can see data2.csv has A3001 in id column but data.csv does not but when I validate
report = validate('datapackage.json',preset='datapackage')

The report does not show any error

Can foreign key constraint check be added to goodtables-py.
diff --git a/tests/scenarios/jungle.yml b/tests/scenarios/jungle.yml
index 3910a53..059d230 100644
--- a/tests/scenarios/jungle.yml
+++ b/tests/scenarios/jungle.yml
@@ -22,4 +22,5 @@ gla-250-report-2014-15-P08:
 multilingual:
   source: data/jungle/multilingual.xlsx
   error_limit: 1
-  report: []
+  report:
+    - [1, 2, 2, 'missing-value']
diff --git a/tests/test_cells.py b/tests/test_cells.py
index d759e46..0347ffe 100644
--- a/tests/test_cells.py
+++ b/tests/test_cells.py
@@ -83,7 +83,7 @@ class TestCells(object):
 
         cells = goodtables.cells.create_cells(headers, schema_fields, values)
         expected_cells = [
-            goodtables.cells.create_cell('name', '', 'name_schema', 1),
+            goodtables.cells.create_cell('name', None, 'name_schema', 1),
             goodtables.cells.create_cell('value', 51, 'value_schema', 2),
         ]
 
diff --git a/tests/test_validate.py b/tests/test_validate.py
index 6892448..b2dd8d7 100644
--- a/tests/test_validate.py
+++ b/tests/test_validate.py
@@ -7,6 +7,8 @@ from __future__ import unicode_literals
 import sys
 import json
 import pytest
+from pprint import pprint
+from copy import deepcopy
 from importlib import import_module
 from goodtables import validate, init_datapackage
 
@@ -146,6 +148,142 @@ def test_source_pathlib_path_datapackage():
     assert report['valid']
 
 
+# Foreign Keys
+
+FK_DESCRIPTOR = {
+  'resources': [
+    {
+      'name': 'cities',
+      'data': [
+        ['id', 'name', 'next_id'],
+        [1, 'london', 2],
+        [2, 'paris', 3],
+        [3, 'rome', 4],
+        [4, 'rio', None],
+      ],
+      'schema': {
+        'fields': [
+          {'name': 'id', 'type': 'integer'},
+          {'name': 'name', 'type': 'string'},
+          {'name': 'next_id', 'type': 'integer'},
+        ],
+        'foreignKeys': [
+          {
+            'fields': 'next_id',
+            'reference': {'resource': '', 'fields': 'id'},
+          },
+          {
+            'fields': 'id',
+            'reference': {'resource': 'people', 'fields': 'label'},
+          },
+        ],
+      },
+    }, {
+      'name': 'people',
+      'data': [
+        ['label', 'population'],
+        [1, 8],
+        [2, 2],
+        [3, 3],
+        [4, 6],
+      ],
+    },
+  ],
+}
+
+
+def test_foreign_key(log):
+    descriptor = deepcopy(FK_DESCRIPTOR)
+    report = validate(descriptor, checks=['foreign-key'])
+    assert log(report) == []
+
+
+def test_foreign_key_not_defined_foreign_keys(log):
+    descriptor = deepcopy(FK_DESCRIPTOR)
+    del descriptor['resources'][0]['schema']['foreignKeys']
+    report = validate(descriptor, checks=['foreign-key'])
+    assert log(report) == []
+
+
+def test_foreign_key_source_is_not_datapackage(log):
+    report = validate('data/valid.csv', checks=['foreign-key'])
+    assert log(report) == []
+
+
+def test_foreign_key_self_referenced_resource_violation(log):
+    descriptor = deepcopy(FK_DESCRIPTOR)
+    del descriptor['resources'][0]['data'][4]
+    report = validate(descriptor, checks=['foreign-key'])
+    assert log(report) == [
+        (1, 4, None, 'foreign-key'),
+    ]
+
+
+def test_foreign_key_internal_resource_violation(log):
+    descriptor = deepcopy(FK_DESCRIPTOR)
+    del descriptor['resources'][1]['data'][4]
+    report = validate(descriptor, checks=['foreign-key'])
+    assert log(report) == [
+        (1, 5, None, 'foreign-key'),
+    ]
+
+
+def test_foreign_key_internal_resource_violation_non_existent(log):
+    descriptor = deepcopy(FK_DESCRIPTOR)
+    del descriptor['resources'][1]
+    report = validate(descriptor, checks=['foreign-key'])
+    assert log(report) == [
+        (1, 2, None, 'foreign-key'),
+        (1, 3, None, 'foreign-key'),
+        (1, 4, None, 'foreign-key'),
+        (1, 5, None, 'foreign-key'),
+    ]
+
+
+def test_foreign_key_external_resource(log):
+    descriptor = 'data/datapackages_linked/cities/datapackage.json'
+    report = validate(descriptor, checks=['structure', 'schema', 'foreign-key'])
+    assert log(report) == []
+
+
+def test_foreign_key_external_resource_errors(log):
+    descriptor = 'data/datapackages_linked_errors/cities/datapackage.json'
+    report = validate(descriptor, checks=['structure', 'schema', 'foreign-key'])
+    assert log(report) == [
+        (1, 4, None, 'foreign-key'),  # self-referenced
+        (1, 4, None, 'foreign-key'),  # external
+    ]
+
+
+def test_foreign_key_external_resource_remote_datapackage(log):
+    descriptor = {
+        'resources': [{
+          'name': 'countries',
+          'data': [
+            ['Country Code', 'Country Name'],
+            ['PRT', 'Portugal'],
+            ['DRL', 'Dreamland'],
+          ],
+          'schema': {
+            'fields': [
+              {'name': 'Country Code', 'type': 'string'},
+              {'name': 'Country Name', 'type': 'string'},
+            ],
+            'foreignKeys': [
+              {
+                'fields': 'Country Code',
+                'reference': {'package': 'https://raw.githubusercontent.com/datasets/gdp/master/datapackage.json', 'resource': 'gdp', 'fields': 'Country Code'},
+              },
+            ],
+          },
+          }]
+    }
+    report = validate(descriptor, checks=['structure', 'schema', 'foreign-key'])
+    assert log(report) == [
+        (1, 3, None, 'foreign-key'),
+    ]
+
+
 # Issues
 
 def test_composite_primary_key_unique_issue_215(log):

pip install --upgrade -e .[develop,ods]
parse_log_pytest
pytest
pytest
apt-get update apt-get install -y gcc
3.6
pytest --no-header -rA --tb=line --color=no -p no:cacheprovider -W ignore::DeprecationWarning
attrs @ file:///opt/conda/conda-bld/attrs_1642510447205/work
boto3==1.23.10
botocore==1.26.10
cached-property==1.5.2
certifi==2021.5.30
chardet==5.0.0
charset-normalizer==2.0.12
click==8.0.4
click-default-group==1.2.4
coverage==6.2
dataflows-tabulator==1.54.3
datapackage==1.15.4
distlib==0.3.9
docutils==0.18.1
et-xmlfile==1.1.0
ezodf==0.3.2
filelock==3.4.1
-e git+https://github.com/frictionlessdata/goodtables-py.git@39585e0060f547f167adc31e063245b0aa1d44ba#egg=goodtables
greenlet==2.0.2
idna==3.10
ijson==3.3.0
importlib-metadata==4.8.3
importlib-resources==5.4.0
iniconfig @ file:///home/linux1/recipes/ci/iniconfig_1610983019677/work
isodate==0.6.1
jmespath==0.10.0
jsonlines==3.1.0
jsonpointer==2.3
jsonschema==3.2.0
linear-tsv==1.1.0
lxml==5.3.1
mccabe==0.7.0
mock==5.2.0
more-itertools @ file:///tmp/build/80754af9/more-itertools_1637733554872/work
openpyxl==3.1.3
packaging @ file:///tmp/build/80754af9/packaging_1637314298585/work
platformdirs==2.4.0
pluggy @ file:///tmp/build/80754af9/pluggy_1615976315926/work
py @ file:///opt/conda/conda-bld/py_1644396412707/work
pycodestyle==2.10.0
pydocstyle==6.3.0
pyflakes==3.0.1
pylama==7.7.1
pyparsing @ file:///tmp/build/80754af9/pyparsing_1635766073266/work
pyrsistent==0.18.0
pytest==6.2.4
pytest-cov==4.0.0
python-dateutil==2.9.0.post0
PyYAML==6.0.1
requests==2.27.1
rfc3986==1.5.0
s3transfer==0.5.2
simpleeval==0.9.13
six==1.17.0
snowballstemmer==2.2.0
SQLAlchemy==1.4.54
statistics==1.0.3.5
tableschema==1.21.0
tabulator==1.53.5
toml @ file:///tmp/build/80754af9/toml_1616166611790/work
tomli==1.2.3
tox==3.28.0
typing_extensions @ file:///opt/conda/conda-bld/typing_extensions_1647553014482/work
unicodecsv==0.14.1
urllib3==1.26.20
virtualenv==20.17.1
xlrd==2.0.1
zipp @ file:///tmp/build/80754af9/zipp_1633618647012/work

name: goodtables-py
channels:
  - defaults
  - https://repo.anaconda.com/pkgs/main
  - https://repo.anaconda.com/pkgs/r
  - conda-forge
dependencies:
  - _libgcc_mutex=0.1=main
  - _openmp_mutex=5.1=1_gnu
  - attrs=21.4.0=pyhd3eb1b0_0
  - ca-certificates=2025.2.25=h06a4308_0
  - certifi=2021.5.30=py36h06a4308_0
  - importlib_metadata=4.8.1=hd3eb1b0_0
  - iniconfig=1.1.1=pyhd3eb1b0_0
  - ld_impl_linux-64=2.40=h12ee557_0
  - libffi=3.3=he6710b0_2
  - libgcc-ng=11.2.0=h1234567_1
  - libgomp=11.2.0=h1234567_1
  - libstdcxx-ng=11.2.0=h1234567_1
  - more-itertools=8.12.0=pyhd3eb1b0_0
  - ncurses=6.4=h6a678d5_0
  - openssl=1.1.1w=h7f8727e_0
  - packaging=21.3=pyhd3eb1b0_0
  - pip=21.2.2=py36h06a4308_0
  - pluggy=0.13.1=py36h06a4308_0
  - py=1.11.0=pyhd3eb1b0_0
  - pyparsing=3.0.4=pyhd3eb1b0_0
  - pytest=6.2.4=py36h06a4308_2
  - python=3.6.13=h12debd9_1
  - readline=8.2=h5eee18b_0
  - setuptools=58.0.4=py36h06a4308_0
  - sqlite=3.45.3=h5eee18b_0
  - tk=8.6.14=h39e8969_0
  - toml=0.10.2=pyhd3eb1b0_0
  - typing_extensions=4.1.1=pyh06a4308_0
  - wheel=0.37.1=pyhd3eb1b0_0
  - xz=5.6.4=h5eee18b_1
  - zipp=3.6.0=pyhd3eb1b0_0
  - zlib=1.2.13=h5eee18b_1
  - pip:
      - boto3==1.23.10
      - botocore==1.26.10
      - cached-property==1.5.2
      - chardet==5.0.0
      - charset-normalizer==2.0.12
      - click==8.0.4
      - click-default-group==1.2.4
      - coverage==6.2
      - dataflows-tabulator==1.54.3
      - datapackage==1.15.4
      - distlib==0.3.9
      - docutils==0.18.1
      - et-xmlfile==1.1.0
      - ezodf==0.3.2
      - filelock==3.4.1
      - greenlet==2.0.2
      - idna==3.10
      - ijson==3.3.0
      - importlib-metadata==4.8.3
      - importlib-resources==5.4.0
      - isodate==0.6.1
      - jmespath==0.10.0
      - jsonlines==3.1.0
      - jsonpointer==2.3
      - jsonschema==3.2.0
      - linear-tsv==1.1.0
      - lxml==5.3.1
      - mccabe==0.7.0
      - mock==5.2.0
      - openpyxl==3.1.3
      - platformdirs==2.4.0
      - pycodestyle==2.10.0
      - pydocstyle==6.3.0
      - pyflakes==3.0.1
      - pylama==7.7.1
      - pyrsistent==0.18.0
      - pytest-cov==4.0.0
      - python-dateutil==2.9.0.post0
      - pyyaml==6.0.1
      - requests==2.27.1
      - rfc3986==1.5.0
      - s3transfer==0.5.2
      - simpleeval==0.9.13
      - six==1.17.0
      - snowballstemmer==2.2.0
      - sqlalchemy==1.4.54
      - statistics==1.0.3.5
      - tableschema==1.21.0
      - tabulator==1.53.5
      - tomli==1.2.3
      - tox==3.28.0
      - unicodecsv==0.14.1
      - urllib3==1.26.20
      - virtualenv==20.17.1
      - xlrd==2.0.1
prefix: /opt/conda/envs/goodtables-py

tests/test_cells.py::TestCells::test_create_cells_with_none_values tests/test_validate.py::test_foreign_key tests/test_validate.py::test_foreign_key_not_defined_foreign_keys tests/test_validate.py::test_foreign_key_source_is_not_datapackage tests/test_validate.py::test_foreign_key_self_referenced_resource_violation tests/test_validate.py::test_foreign_key_internal_resource_violation tests/test_validate.py::test_foreign_key_internal_resource_violation_non_existent tests/test_validate.py::test_foreign_key_external_resource_errors tests/test_validate.py::test_foreign_key_external_resource_remote_datapackage
tests/test_cells.py::TestCells::test_create_header_cells tests/test_cells.py::TestCells::test_create_cells_with_values tests/test_cells.py::TestCells::test_create_cells_with_incomplete_values tests/test_cells.py::TestCells::test_dict_has_expected_keys tests/test_cells.py::TestCells::test_dict_removes_none_values tests/test_validate.py::test_validate_infer_table tests/test_validate.py::test_validate_infer_datapackage_path tests/test_validate.py::test_validate_infer_datapackage_dict tests/test_validate.py::test_validate_infer_nested tests/test_validate.py::test_validate_report_scheme_format_encoding tests/test_validate.py::test_validate_report_schema tests/test_validate.py::test_validate_report_schema_infer_schema tests/test_validate.py::test_validate_nested_checks tests/test_validate.py::test_validate_datapackage_dialect_header_false tests/test_validate.py::test_source_pathlib_path_table tests/test_validate.py::test_source_pathlib_path_datapackage tests/test_validate.py::test_foreign_key_external_resource tests/test_validate.py::test_composite_primary_key_unique_issue_215 tests/test_validate.py::test_composite_primary_key_not_unique_issue_215 tests/test_validate.py::test_validate_infer_fields_issue_223 tests/test_validate.py::test_validate_infer_fields_issue_225 tests/test_validate.py::TestValidateInitDatapackage::test_datapackage_is_correct
data/datapackages_linked_errors/cities/cities.csv goodtables/cells.py data/datapackages_linked/people/datapackage.json data/datapackages_linked_errors/cities/datapackage.json setup.py data/datapackages_linked_errors/people/datapackage.json goodtables/config.py data/datapackages_linked/people/people.csv pylama.ini data/datapackages_linked/cities/cities.csv data/datapackages_linked_errors/people/people.csv goodtables/contrib/checks/foreign_key.py README.md goodtables/checks/type_or_format_error.py goodtables/inspector.py goodtables/presets/datapackage.py goodtables/checks/required_constraint.py data/datapackages_linked/cities/datapackage.json
data/datapackages_linked_errors/cities/cities.csv goodtables/cells.py data/datapackages_linked/people/datapackage.json data/datapackages_linked_errors/cities/datapackage.json setup.py data/datapackages_linked_errors/people/datapackage.json goodtables/config.py data/datapackages_linked/people/people.csv pylama.ini data/datapackages_linked/cities/cities.csv data/datapackages_linked_errors/people/people.csv goodtables/contrib/checks/foreign_key.py README.md goodtables/checks/type_or_format_error.py goodtables/inspector.py goodtables/presets/datapackage.py goodtables/checks/required_constraint.py data/datapackages_linked/cities/datapackage.json