We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
Exception when deleting resource if datastore table should exist but does not
**2.8.4**

**Describe the bug**
If for whatever reason, you end up with a resource for which datastore_active is set in the resource extras, but the datastore table does not actually exist, an exception is thown when trying to delete this resource.

**Steps to reproduce**
1. Create a resource and make sure data is uploaded to the datastore
2. Manually delete the database table of this resource from the database
3. Try to delete this resource via the ckan UI
4. An exception is thrown

**Expected behavior**
Before deleting, check whether the datastore table actually exists. If it doesn't exist, just skip the delete step. Better than throwing an exception.

**Additional details**
Not sure how I managed to get into this inconsistent state. Might not even be CKAN's fault since we had some issues with our persistence infrastructure/volumes.

Stack trace here:
```
File '/srv/app/src/ckan/ckan/controllers/package.py', line 1175 in resource_delete
  get_action('resource_delete')(context, {'id': resource_id})
File '/srv/app/src/ckan/ckan/logic/__init__.py', line 466 in wrapped
  result = _action(context, data_dict, **kw)
File '/srv/app/src/ckan/ckan/logic/action/delete.py', line 204 in resource_delete
  plugin.after_delete(context, pkg_dict.get('resources', []))
File '/srv/app/src/ckan/ckanext/datastore/plugin.py', line 161 in after_delete
  'resource_id': res.id,
File '/srv/app/src/ckan/ckanext/datastore/backend/postgres.py', line 1720 in delete
  data_dict['resource_id'])
File '/usr/lib/python2.7/site-packages/sqlalchemy/engine/base.py', line 939 in execute
  return self._execute_text(object, multiparams, params)
File '/usr/lib/python2.7/site-packages/sqlalchemy/engine/base.py', line 1097 in _execute_text
  statement, parameters
File '/usr/lib/python2.7/site-packages/sqlalchemy/engine/base.py', line 1189 in _execute_context
  context)
File '/usr/lib/python2.7/site-packages/sqlalchemy/engine/base.py', line 1402 in _handle_dbapi_exception
  exc_info
File '/usr/lib/python2.7/site-packages/sqlalchemy/util/compat.py', line 203 in raise_from_cause
  reraise(type(exception), exception, tb=exc_tb, cause=cause)
File '/usr/lib/python2.7/site-packages/sqlalchemy/engine/base.py', line 1182 in _execute_context
  context)
File '/usr/lib/python2.7/site-packages/sqlalchemy/engine/default.py', line 470 in do_execute
  cursor.execute(statement, parameters)
ProgrammingError: (psycopg2.ProgrammingError) table "f03c4532-bc47-4ca0-bf73-f96e42082f49" does not exist
 [SQL: 'DROP TABLE "f03c4532-bc47-4ca0-bf73-f96e42082f49" CASCADE']
```

I will provide a pull request.

--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `ckanext/datastore/plugin.py`
Content:
```
1 # encoding: utf-8
2 
3 import logging
4 
5 from six import string_types
6 
7 import ckan.plugins as p
8 import ckan.logic as logic
9 import ckan.model as model
10 from ckan.model.core import State
11 
12 import ckanext.datastore.helpers as datastore_helpers
13 import ckanext.datastore.logic.action as action
14 import ckanext.datastore.logic.auth as auth
15 import ckanext.datastore.interfaces as interfaces
16 from ckanext.datastore.backend import (
17     DatastoreException,
18     _parse_sort_clause,
19     DatastoreBackend
20 )
21 from ckanext.datastore.backend.postgres import DatastorePostgresqlBackend
22 import ckanext.datastore.blueprint as view
23 
24 log = logging.getLogger(__name__)
25 _get_or_bust = logic.get_or_bust
26 
27 DEFAULT_FORMATS = []
28 
29 ValidationError = p.toolkit.ValidationError
30 
31 
32 class DatastorePlugin(p.SingletonPlugin):
33     p.implements(p.IConfigurable, inherit=True)
34     p.implements(p.IConfigurer)
35     p.implements(p.IActions)
36     p.implements(p.IAuthFunctions)
37     p.implements(p.IRoutes, inherit=True)
38     p.implements(p.IResourceController, inherit=True)
39     p.implements(p.ITemplateHelpers)
40     p.implements(p.IForkObserver, inherit=True)
41     p.implements(interfaces.IDatastore, inherit=True)
42     p.implements(interfaces.IDatastoreBackend, inherit=True)
43     p.implements(p.IBlueprint)
44 
45     resource_show_action = None
46 
47     def __new__(cls, *args, **kwargs):
48         idatastore_extensions = p.PluginImplementations(interfaces.IDatastore)
49         idatastore_extensions = idatastore_extensions.extensions()
50 
51         if idatastore_extensions and idatastore_extensions[0].__class__ != cls:
52             msg = ('The "datastore" plugin must be the first IDatastore '
53                    'plugin loaded. Change the order it is loaded in '
54                    '"ckan.plugins" in your CKAN .ini file and try again.')
55             raise DatastoreException(msg)
56 
57         return super(cls, cls).__new__(cls, *args, **kwargs)
58 
59     # IDatastoreBackend
60 
61     def register_backends(self):
62         return {
63             'postgresql': DatastorePostgresqlBackend,
64             'postgres': DatastorePostgresqlBackend,
65         }
66 
67     # IConfigurer
68 
69     def update_config(self, config):
70         DatastoreBackend.register_backends()
71         DatastoreBackend.set_active_backend(config)
72 
73         templates_base = config.get('ckan.base_templates_folder')
74 
75         p.toolkit.add_template_directory(config, templates_base)
76         self.backend = DatastoreBackend.get_active_backend()
77 
78     # IConfigurable
79 
80     def configure(self, config):
81         self.config = config
82         self.backend.configure(config)
83 
84     # IActions
85 
86     def get_actions(self):
87         actions = {
88             'datastore_create': action.datastore_create,
89             'datastore_upsert': action.datastore_upsert,
90             'datastore_delete': action.datastore_delete,
91             'datastore_search': action.datastore_search,
92             'datastore_info': action.datastore_info,
93             'datastore_function_create': action.datastore_function_create,
94             'datastore_function_delete': action.datastore_function_delete,
95             'datastore_run_triggers': action.datastore_run_triggers,
96         }
97         if getattr(self.backend, 'enable_sql_search', False):
98             # Only enable search_sql if the config/backend does not disable it
99             actions.update({
100                 'datastore_search_sql': action.datastore_search_sql,
101             })
102         return actions
103 
104     # IAuthFunctions
105 
106     def get_auth_functions(self):
107         return {
108             'datastore_create': auth.datastore_create,
109             'datastore_upsert': auth.datastore_upsert,
110             'datastore_delete': auth.datastore_delete,
111             'datastore_info': auth.datastore_info,
112             'datastore_search': auth.datastore_search,
113             'datastore_search_sql': auth.datastore_search_sql,
114             'datastore_change_permissions': auth.datastore_change_permissions,
115             'datastore_function_create': auth.datastore_function_create,
116             'datastore_function_delete': auth.datastore_function_delete,
117             'datastore_run_triggers': auth.datastore_run_triggers,
118         }
119 
120     # IResourceController
121 
122     def before_show(self, resource_dict):
123         # Modify the resource url of datastore resources so that
124         # they link to the datastore dumps.
125         if resource_dict.get('url_type') == 'datastore':
126             resource_dict['url'] = p.toolkit.url_for(
127                 'datastore.dump', resource_id=resource_dict['id'],
128                 qualified=True)
129 
130         if 'datastore_active' not in resource_dict:
131             resource_dict[u'datastore_active'] = False
132 
133         return resource_dict
134 
135     def after_delete(self, context, resources):
136         model = context['model']
137         pkg = context['package']
138         res_query = model.Session.query(model.Resource)
139         query = res_query.filter(
140             model.Resource.package_id == pkg.id,
141             model.Resource.state == State.DELETED
142         )
143         deleted = [
144             res for res in query.all()
145             if res.extras.get('datastore_active') is True]
146 
147         for res in deleted:
148             self.backend.delete(context, {
149                 'resource_id': res.id,
150             })
151             res.extras['datastore_active'] = False
152             res_query.filter_by(id=res.id).update(
153                 {'extras': res.extras}, synchronize_session=False)
154 
155     # IDatastore
156 
157     def datastore_validate(self, context, data_dict, fields_types):
158         column_names = list(fields_types.keys())
159 
160         filters = data_dict.get('filters', {})
161         for key in list(filters.keys()):
162             if key in fields_types:
163                 del filters[key]
164 
165         q = data_dict.get('q')
166         if q:
167             if isinstance(q, string_types):
168                 del data_dict['q']
169                 column_names.append(u'rank')
170             elif isinstance(q, dict):
171                 for key in list(q.keys()):
172                     if key in fields_types and isinstance(q[key],
173                                                           string_types):
174                         column_names.append(u'rank ' + key)
175                         del q[key]
176 
177         fields = data_dict.get('fields')
178         if fields:
179             data_dict['fields'] = list(set(fields) - set(column_names))
180 
181         language = data_dict.get('language')
182         if language:
183             if isinstance(language, string_types):
184                 del data_dict['language']
185 
186         plain = data_dict.get('plain')
187         if plain:
188             if isinstance(plain, bool):
189                 del data_dict['plain']
190 
191         distinct = data_dict.get('distinct')
192         if distinct:
193             if isinstance(distinct, bool):
194                 del data_dict['distinct']
195 
196         sort_clauses = data_dict.get('sort')
197         if sort_clauses:
198             invalid_clauses = [
199                 c for c in sort_clauses
200                 if not _parse_sort_clause(
201                     c, fields_types
202                 )
203             ]
204             data_dict['sort'] = invalid_clauses
205 
206         limit = data_dict.get('limit')
207         if limit:
208             is_positive_int = datastore_helpers.validate_int(limit,
209                                                              non_negative=True)
210             is_all = isinstance(limit, string_types) and limit.lower() == 'all'
211             if is_positive_int or is_all:
212                 del data_dict['limit']
213 
214         offset = data_dict.get('offset')
215         if offset:
216             is_positive_int = datastore_helpers.validate_int(offset,
217                                                              non_negative=True)
218             if is_positive_int:
219                 del data_dict['offset']
220 
221         return data_dict
222 
223     def datastore_delete(self, context, data_dict, fields_types, query_dict):
224         hook = getattr(self.backend, 'datastore_delete', None)
225         if hook:
226             query_dict = hook(context, data_dict, fields_types, query_dict)
227         return query_dict
228 
229     def datastore_search(self, context, data_dict, fields_types, query_dict):
230         hook = getattr(self.backend, 'datastore_search', None)
231         if hook:
232             query_dict = hook(context, data_dict, fields_types, query_dict)
233         return query_dict
234 
235     def get_helpers(self):
236         return {
237             'datastore_dictionary': datastore_helpers.datastore_dictionary}
238 
239     # IForkObserver
240 
241     def before_fork(self):
242         try:
243             before_fork = self.backend.before_fork
244         except AttributeError:
245             pass
246         else:
247             before_fork()
248 
249     # IBlueprint
250 
251     def get_blueprint(self):
252         u'''Return a Flask Blueprint object to be registered by the app.'''
253 
254         return view.datastore
255
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/ckanext/datastore/plugin.py b/ckanext/datastore/plugin.py
--- a/ckanext/datastore/plugin.py
+++ b/ckanext/datastore/plugin.py
@@ -145,9 +145,10 @@
             if res.extras.get('datastore_active') is True]
 
         for res in deleted:
-            self.backend.delete(context, {
-                'resource_id': res.id,
-            })
+            if self.backend.resource_exists(res.id):
+                self.backend.delete(context, {
+                    'resource_id': res.id,
+                })
             res.extras['datastore_active'] = False
             res_query.filter_by(id=res.id).update(
                 {'extras': res.extras}, synchronize_session=False)

{"golden_diff": "diff --git a/ckanext/datastore/plugin.py b/ckanext/datastore/plugin.py\n--- a/ckanext/datastore/plugin.py\n+++ b/ckanext/datastore/plugin.py\n@@ -145,9 +145,10 @@\n             if res.extras.get('datastore_active') is True]\n \n         for res in deleted:\n-            self.backend.delete(context, {\n-                'resource_id': res.id,\n-            })\n+            if self.backend.resource_exists(res.id):\n+                self.backend.delete(context, {\n+                    'resource_id': res.id,\n+                })\n             res.extras['datastore_active'] = False\n             res_query.filter_by(id=res.id).update(\n                 {'extras': res.extras}, synchronize_session=False)\n", "issue": "Exception when deleting resource if datastore table should exist but does not\n**2.8.4**\r\n\r\n**Describe the bug**\r\nIf for whatever reason, you end up with a resource for which datastore_active is set in the resource extras, but the datastore table does not actually exist, an exception is thown when trying to delete this resource.\r\n\r\n**Steps to reproduce**\r\n1. Create a resource and make sure data is uploaded to the datastore\r\n2. Manually delete the database table of this resource from the database\r\n3. Try to delete this resource via the ckan UI\r\n4. An exception is thrown\r\n\r\n**Expected behavior**\r\nBefore deleting, check whether the datastore table actually exists. If it doesn't exist, just skip the delete step. Better than throwing an exception.\r\n\r\n**Additional details**\r\nNot sure how I managed to get into this inconsistent state. Might not even be CKAN's fault since we had some issues with our persistence infrastructure/volumes.\r\n\r\nStack trace here:\r\n```\r\nFile '/srv/app/src/ckan/ckan/controllers/package.py', line 1175 in resource_delete\r\n  get_action('resource_delete')(context, {'id': resource_id})\r\nFile '/srv/app/src/ckan/ckan/logic/__init__.py', line 466 in wrapped\r\n  result = _action(context, data_dict, **kw)\r\nFile '/srv/app/src/ckan/ckan/logic/action/delete.py', line 204 in resource_delete\r\n  plugin.after_delete(context, pkg_dict.get('resources', []))\r\nFile '/srv/app/src/ckan/ckanext/datastore/plugin.py', line 161 in after_delete\r\n  'resource_id': res.id,\r\nFile '/srv/app/src/ckan/ckanext/datastore/backend/postgres.py', line 1720 in delete\r\n  data_dict['resource_id'])\r\nFile '/usr/lib/python2.7/site-packages/sqlalchemy/engine/base.py', line 939 in execute\r\n  return self._execute_text(object, multiparams, params)\r\nFile '/usr/lib/python2.7/site-packages/sqlalchemy/engine/base.py', line 1097 in _execute_text\r\n  statement, parameters\r\nFile '/usr/lib/python2.7/site-packages/sqlalchemy/engine/base.py', line 1189 in _execute_context\r\n  context)\r\nFile '/usr/lib/python2.7/site-packages/sqlalchemy/engine/base.py', line 1402 in _handle_dbapi_exception\r\n  exc_info\r\nFile '/usr/lib/python2.7/site-packages/sqlalchemy/util/compat.py', line 203 in raise_from_cause\r\n  reraise(type(exception), exception, tb=exc_tb, cause=cause)\r\nFile '/usr/lib/python2.7/site-packages/sqlalchemy/engine/base.py', line 1182 in _execute_context\r\n  context)\r\nFile '/usr/lib/python2.7/site-packages/sqlalchemy/engine/default.py', line 470 in do_execute\r\n  cursor.execute(statement, parameters)\r\nProgrammingError: (psycopg2.ProgrammingError) table \"f03c4532-bc47-4ca0-bf73-f96e42082f49\" does not exist\r\n [SQL: 'DROP TABLE \"f03c4532-bc47-4ca0-bf73-f96e42082f49\" CASCADE']\r\n```\r\n\r\nI will provide a pull request.\n", "before_files": [{"content": "# encoding: utf-8\n\nimport logging\n\nfrom six import string_types\n\nimport ckan.plugins as p\nimport ckan.logic as logic\nimport ckan.model as model\nfrom ckan.model.core import State\n\nimport ckanext.datastore.helpers as datastore_helpers\nimport ckanext.datastore.logic.action as action\nimport ckanext.datastore.logic.auth as auth\nimport ckanext.datastore.interfaces as interfaces\nfrom ckanext.datastore.backend import (\n    DatastoreException,\n    _parse_sort_clause,\n    DatastoreBackend\n)\nfrom ckanext.datastore.backend.postgres import DatastorePostgresqlBackend\nimport ckanext.datastore.blueprint as view\n\nlog = logging.getLogger(__name__)\n_get_or_bust = logic.get_or_bust\n\nDEFAULT_FORMATS = []\n\nValidationError = p.toolkit.ValidationError\n\n\nclass DatastorePlugin(p.SingletonPlugin):\n    p.implements(p.IConfigurable, inherit=True)\n    p.implements(p.IConfigurer)\n    p.implements(p.IActions)\n    p.implements(p.IAuthFunctions)\n    p.implements(p.IRoutes, inherit=True)\n    p.implements(p.IResourceController, inherit=True)\n    p.implements(p.ITemplateHelpers)\n    p.implements(p.IForkObserver, inherit=True)\n    p.implements(interfaces.IDatastore, inherit=True)\n    p.implements(interfaces.IDatastoreBackend, inherit=True)\n    p.implements(p.IBlueprint)\n\n    resource_show_action = None\n\n    def __new__(cls, *args, **kwargs):\n        idatastore_extensions = p.PluginImplementations(interfaces.IDatastore)\n        idatastore_extensions = idatastore_extensions.extensions()\n\n        if idatastore_extensions and idatastore_extensions[0].__class__ != cls:\n            msg = ('The \"datastore\" plugin must be the first IDatastore '\n                   'plugin loaded. Change the order it is loaded in '\n                   '\"ckan.plugins\" in your CKAN .ini file and try again.')\n            raise DatastoreException(msg)\n\n        return super(cls, cls).__new__(cls, *args, **kwargs)\n\n    # IDatastoreBackend\n\n    def register_backends(self):\n        return {\n            'postgresql': DatastorePostgresqlBackend,\n            'postgres': DatastorePostgresqlBackend,\n        }\n\n    # IConfigurer\n\n    def update_config(self, config):\n        DatastoreBackend.register_backends()\n        DatastoreBackend.set_active_backend(config)\n\n        templates_base = config.get('ckan.base_templates_folder')\n\n        p.toolkit.add_template_directory(config, templates_base)\n        self.backend = DatastoreBackend.get_active_backend()\n\n    # IConfigurable\n\n    def configure(self, config):\n        self.config = config\n        self.backend.configure(config)\n\n    # IActions\n\n    def get_actions(self):\n        actions = {\n            'datastore_create': action.datastore_create,\n            'datastore_upsert': action.datastore_upsert,\n            'datastore_delete': action.datastore_delete,\n            'datastore_search': action.datastore_search,\n            'datastore_info': action.datastore_info,\n            'datastore_function_create': action.datastore_function_create,\n            'datastore_function_delete': action.datastore_function_delete,\n            'datastore_run_triggers': action.datastore_run_triggers,\n        }\n        if getattr(self.backend, 'enable_sql_search', False):\n            # Only enable search_sql if the config/backend does not disable it\n            actions.update({\n                'datastore_search_sql': action.datastore_search_sql,\n            })\n        return actions\n\n    # IAuthFunctions\n\n    def get_auth_functions(self):\n        return {\n            'datastore_create': auth.datastore_create,\n            'datastore_upsert': auth.datastore_upsert,\n            'datastore_delete': auth.datastore_delete,\n            'datastore_info': auth.datastore_info,\n            'datastore_search': auth.datastore_search,\n            'datastore_search_sql': auth.datastore_search_sql,\n            'datastore_change_permissions': auth.datastore_change_permissions,\n            'datastore_function_create': auth.datastore_function_create,\n            'datastore_function_delete': auth.datastore_function_delete,\n            'datastore_run_triggers': auth.datastore_run_triggers,\n        }\n\n    # IResourceController\n\n    def before_show(self, resource_dict):\n        # Modify the resource url of datastore resources so that\n        # they link to the datastore dumps.\n        if resource_dict.get('url_type') == 'datastore':\n            resource_dict['url'] = p.toolkit.url_for(\n                'datastore.dump', resource_id=resource_dict['id'],\n                qualified=True)\n\n        if 'datastore_active' not in resource_dict:\n            resource_dict[u'datastore_active'] = False\n\n        return resource_dict\n\n    def after_delete(self, context, resources):\n        model = context['model']\n        pkg = context['package']\n        res_query = model.Session.query(model.Resource)\n        query = res_query.filter(\n            model.Resource.package_id == pkg.id,\n            model.Resource.state == State.DELETED\n        )\n        deleted = [\n            res for res in query.all()\n            if res.extras.get('datastore_active') is True]\n\n        for res in deleted:\n            self.backend.delete(context, {\n                'resource_id': res.id,\n            })\n            res.extras['datastore_active'] = False\n            res_query.filter_by(id=res.id).update(\n                {'extras': res.extras}, synchronize_session=False)\n\n    # IDatastore\n\n    def datastore_validate(self, context, data_dict, fields_types):\n        column_names = list(fields_types.keys())\n\n        filters = data_dict.get('filters', {})\n        for key in list(filters.keys()):\n            if key in fields_types:\n                del filters[key]\n\n        q = data_dict.get('q')\n        if q:\n            if isinstance(q, string_types):\n                del data_dict['q']\n                column_names.append(u'rank')\n            elif isinstance(q, dict):\n                for key in list(q.keys()):\n                    if key in fields_types and isinstance(q[key],\n                                                          string_types):\n                        column_names.append(u'rank ' + key)\n                        del q[key]\n\n        fields = data_dict.get('fields')\n        if fields:\n            data_dict['fields'] = list(set(fields) - set(column_names))\n\n        language = data_dict.get('language')\n        if language:\n            if isinstance(language, string_types):\n                del data_dict['language']\n\n        plain = data_dict.get('plain')\n        if plain:\n            if isinstance(plain, bool):\n                del data_dict['plain']\n\n        distinct = data_dict.get('distinct')\n        if distinct:\n            if isinstance(distinct, bool):\n                del data_dict['distinct']\n\n        sort_clauses = data_dict.get('sort')\n        if sort_clauses:\n            invalid_clauses = [\n                c for c in sort_clauses\n                if not _parse_sort_clause(\n                    c, fields_types\n                )\n            ]\n            data_dict['sort'] = invalid_clauses\n\n        limit = data_dict.get('limit')\n        if limit:\n            is_positive_int = datastore_helpers.validate_int(limit,\n                                                             non_negative=True)\n            is_all = isinstance(limit, string_types) and limit.lower() == 'all'\n            if is_positive_int or is_all:\n                del data_dict['limit']\n\n        offset = data_dict.get('offset')\n        if offset:\n            is_positive_int = datastore_helpers.validate_int(offset,\n                                                             non_negative=True)\n            if is_positive_int:\n                del data_dict['offset']\n\n        return data_dict\n\n    def datastore_delete(self, context, data_dict, fields_types, query_dict):\n        hook = getattr(self.backend, 'datastore_delete', None)\n        if hook:\n            query_dict = hook(context, data_dict, fields_types, query_dict)\n        return query_dict\n\n    def datastore_search(self, context, data_dict, fields_types, query_dict):\n        hook = getattr(self.backend, 'datastore_search', None)\n        if hook:\n            query_dict = hook(context, data_dict, fields_types, query_dict)\n        return query_dict\n\n    def get_helpers(self):\n        return {\n            'datastore_dictionary': datastore_helpers.datastore_dictionary}\n\n    # IForkObserver\n\n    def before_fork(self):\n        try:\n            before_fork = self.backend.before_fork\n        except AttributeError:\n            pass\n        else:\n            before_fork()\n\n    # IBlueprint\n\n    def get_blueprint(self):\n        u'''Return a Flask Blueprint object to be registered by the app.'''\n\n        return view.datastore\n", "path": "ckanext/datastore/plugin.py"}], "after_files": [{"content": "# encoding: utf-8\n\nimport logging\n\nfrom six import string_types\n\nimport ckan.plugins as p\nimport ckan.logic as logic\nimport ckan.model as model\nfrom ckan.model.core import State\n\nimport ckanext.datastore.helpers as datastore_helpers\nimport ckanext.datastore.logic.action as action\nimport ckanext.datastore.logic.auth as auth\nimport ckanext.datastore.interfaces as interfaces\nfrom ckanext.datastore.backend import (\n    DatastoreException,\n    _parse_sort_clause,\n    DatastoreBackend\n)\nfrom ckanext.datastore.backend.postgres import DatastorePostgresqlBackend\nimport ckanext.datastore.blueprint as view\n\nlog = logging.getLogger(__name__)\n_get_or_bust = logic.get_or_bust\n\nDEFAULT_FORMATS = []\n\nValidationError = p.toolkit.ValidationError\n\n\nclass DatastorePlugin(p.SingletonPlugin):\n    p.implements(p.IConfigurable, inherit=True)\n    p.implements(p.IConfigurer)\n    p.implements(p.IActions)\n    p.implements(p.IAuthFunctions)\n    p.implements(p.IRoutes, inherit=True)\n    p.implements(p.IResourceController, inherit=True)\n    p.implements(p.ITemplateHelpers)\n    p.implements(p.IForkObserver, inherit=True)\n    p.implements(interfaces.IDatastore, inherit=True)\n    p.implements(interfaces.IDatastoreBackend, inherit=True)\n    p.implements(p.IBlueprint)\n\n    resource_show_action = None\n\n    def __new__(cls, *args, **kwargs):\n        idatastore_extensions = p.PluginImplementations(interfaces.IDatastore)\n        idatastore_extensions = idatastore_extensions.extensions()\n\n        if idatastore_extensions and idatastore_extensions[0].__class__ != cls:\n            msg = ('The \"datastore\" plugin must be the first IDatastore '\n                   'plugin loaded. Change the order it is loaded in '\n                   '\"ckan.plugins\" in your CKAN .ini file and try again.')\n            raise DatastoreException(msg)\n\n        return super(cls, cls).__new__(cls, *args, **kwargs)\n\n    # IDatastoreBackend\n\n    def register_backends(self):\n        return {\n            'postgresql': DatastorePostgresqlBackend,\n            'postgres': DatastorePostgresqlBackend,\n        }\n\n    # IConfigurer\n\n    def update_config(self, config):\n        DatastoreBackend.register_backends()\n        DatastoreBackend.set_active_backend(config)\n\n        templates_base = config.get('ckan.base_templates_folder')\n\n        p.toolkit.add_template_directory(config, templates_base)\n        self.backend = DatastoreBackend.get_active_backend()\n\n    # IConfigurable\n\n    def configure(self, config):\n        self.config = config\n        self.backend.configure(config)\n\n    # IActions\n\n    def get_actions(self):\n        actions = {\n            'datastore_create': action.datastore_create,\n            'datastore_upsert': action.datastore_upsert,\n            'datastore_delete': action.datastore_delete,\n            'datastore_search': action.datastore_search,\n            'datastore_info': action.datastore_info,\n            'datastore_function_create': action.datastore_function_create,\n            'datastore_function_delete': action.datastore_function_delete,\n            'datastore_run_triggers': action.datastore_run_triggers,\n        }\n        if getattr(self.backend, 'enable_sql_search', False):\n            # Only enable search_sql if the config/backend does not disable it\n            actions.update({\n                'datastore_search_sql': action.datastore_search_sql,\n            })\n        return actions\n\n    # IAuthFunctions\n\n    def get_auth_functions(self):\n        return {\n            'datastore_create': auth.datastore_create,\n            'datastore_upsert': auth.datastore_upsert,\n            'datastore_delete': auth.datastore_delete,\n            'datastore_info': auth.datastore_info,\n            'datastore_search': auth.datastore_search,\n            'datastore_search_sql': auth.datastore_search_sql,\n            'datastore_change_permissions': auth.datastore_change_permissions,\n            'datastore_function_create': auth.datastore_function_create,\n            'datastore_function_delete': auth.datastore_function_delete,\n            'datastore_run_triggers': auth.datastore_run_triggers,\n        }\n\n    # IResourceController\n\n    def before_show(self, resource_dict):\n        # Modify the resource url of datastore resources so that\n        # they link to the datastore dumps.\n        if resource_dict.get('url_type') == 'datastore':\n            resource_dict['url'] = p.toolkit.url_for(\n                'datastore.dump', resource_id=resource_dict['id'],\n                qualified=True)\n\n        if 'datastore_active' not in resource_dict:\n            resource_dict[u'datastore_active'] = False\n\n        return resource_dict\n\n    def after_delete(self, context, resources):\n        model = context['model']\n        pkg = context['package']\n        res_query = model.Session.query(model.Resource)\n        query = res_query.filter(\n            model.Resource.package_id == pkg.id,\n            model.Resource.state == State.DELETED\n        )\n        deleted = [\n            res for res in query.all()\n            if res.extras.get('datastore_active') is True]\n\n        for res in deleted:\n            if self.backend.resource_exists(res.id):\n                self.backend.delete(context, {\n                    'resource_id': res.id,\n                })\n            res.extras['datastore_active'] = False\n            res_query.filter_by(id=res.id).update(\n                {'extras': res.extras}, synchronize_session=False)\n\n    # IDatastore\n\n    def datastore_validate(self, context, data_dict, fields_types):\n        column_names = list(fields_types.keys())\n\n        filters = data_dict.get('filters', {})\n        for key in list(filters.keys()):\n            if key in fields_types:\n                del filters[key]\n\n        q = data_dict.get('q')\n        if q:\n            if isinstance(q, string_types):\n                del data_dict['q']\n                column_names.append(u'rank')\n            elif isinstance(q, dict):\n                for key in list(q.keys()):\n                    if key in fields_types and isinstance(q[key],\n                                                          string_types):\n                        column_names.append(u'rank ' + key)\n                        del q[key]\n\n        fields = data_dict.get('fields')\n        if fields:\n            data_dict['fields'] = list(set(fields) - set(column_names))\n\n        language = data_dict.get('language')\n        if language:\n            if isinstance(language, string_types):\n                del data_dict['language']\n\n        plain = data_dict.get('plain')\n        if plain:\n            if isinstance(plain, bool):\n                del data_dict['plain']\n\n        distinct = data_dict.get('distinct')\n        if distinct:\n            if isinstance(distinct, bool):\n                del data_dict['distinct']\n\n        sort_clauses = data_dict.get('sort')\n        if sort_clauses:\n            invalid_clauses = [\n                c for c in sort_clauses\n                if not _parse_sort_clause(\n                    c, fields_types\n                )\n            ]\n            data_dict['sort'] = invalid_clauses\n\n        limit = data_dict.get('limit')\n        if limit:\n            is_positive_int = datastore_helpers.validate_int(limit,\n                                                             non_negative=True)\n            is_all = isinstance(limit, string_types) and limit.lower() == 'all'\n            if is_positive_int or is_all:\n                del data_dict['limit']\n\n        offset = data_dict.get('offset')\n        if offset:\n            is_positive_int = datastore_helpers.validate_int(offset,\n                                                             non_negative=True)\n            if is_positive_int:\n                del data_dict['offset']\n\n        return data_dict\n\n    def datastore_delete(self, context, data_dict, fields_types, query_dict):\n        hook = getattr(self.backend, 'datastore_delete', None)\n        if hook:\n            query_dict = hook(context, data_dict, fields_types, query_dict)\n        return query_dict\n\n    def datastore_search(self, context, data_dict, fields_types, query_dict):\n        hook = getattr(self.backend, 'datastore_search', None)\n        if hook:\n            query_dict = hook(context, data_dict, fields_types, query_dict)\n        return query_dict\n\n    def get_helpers(self):\n        return {\n            'datastore_dictionary': datastore_helpers.datastore_dictionary}\n\n    # IForkObserver\n\n    def before_fork(self):\n        try:\n            before_fork = self.backend.before_fork\n        except AttributeError:\n            pass\n        else:\n            before_fork()\n\n    # IBlueprint\n\n    def get_blueprint(self):\n        u'''Return a Flask Blueprint object to be registered by the app.'''\n\n        return view.datastore\n", "path": "ckanext/datastore/plugin.py"}]}