ckanext-csvwmapandtransform 1.0.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (26) hide show
  1. ckanext/csvwmapandtransform/__init__.py +0 -0
  2. ckanext/csvwmapandtransform/action.py +405 -0
  3. ckanext/csvwmapandtransform/assets/script.js +81 -0
  4. ckanext/csvwmapandtransform/assets/style.css +124 -0
  5. ckanext/csvwmapandtransform/assets/webassets.yml +13 -0
  6. ckanext/csvwmapandtransform/auth.py +23 -0
  7. ckanext/csvwmapandtransform/cli.py +18 -0
  8. ckanext/csvwmapandtransform/db.py +397 -0
  9. ckanext/csvwmapandtransform/helpers.py +67 -0
  10. ckanext/csvwmapandtransform/mapper.py +92 -0
  11. ckanext/csvwmapandtransform/plugin.py +140 -0
  12. ckanext/csvwmapandtransform/tasks.py +257 -0
  13. ckanext/csvwmapandtransform/templates/csvwmapandtransform/create_mapping.html +56 -0
  14. ckanext/csvwmapandtransform/templates/csvwmapandtransform/transform.html +108 -0
  15. ckanext/csvwmapandtransform/templates/package/resource_read.html +8 -0
  16. ckanext/csvwmapandtransform/templates/package/snippets/resource_item.html +23 -0
  17. ckanext/csvwmapandtransform/tests/__init__.py +0 -0
  18. ckanext/csvwmapandtransform/views.py +205 -0
  19. ckanext_csvwmapandtransform-1.0.0-py3.13-nspkg.pth +1 -0
  20. ckanext_csvwmapandtransform-1.0.0.dist-info/METADATA +121 -0
  21. ckanext_csvwmapandtransform-1.0.0.dist-info/RECORD +26 -0
  22. ckanext_csvwmapandtransform-1.0.0.dist-info/WHEEL +5 -0
  23. ckanext_csvwmapandtransform-1.0.0.dist-info/entry_points.txt +2 -0
  24. ckanext_csvwmapandtransform-1.0.0.dist-info/licenses/LICENSE +661 -0
  25. ckanext_csvwmapandtransform-1.0.0.dist-info/namespace_packages.txt +1 -0
  26. ckanext_csvwmapandtransform-1.0.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,397 @@
1
+ """
2
+ Abstracts a database. Used for storing logging when it aiembeddings a resource into
3
+ DataStore.
4
+
5
+ Loosely based on ckan-service-provider's db.py
6
+ """
7
+
8
+ """
9
+ Abstracts a database. Used for storing logging when it aiembeddings a resource into
10
+ DataStore.
11
+
12
+ Loosely based on ckan-service-provider's db.py
13
+ """
14
+
15
+ import datetime
16
+ import json
17
+
18
+ import six
19
+ import sqlalchemy
20
+ from ckan.plugins import toolkit
21
+
22
+ ENGINE = None
23
+ _METADATA = None
24
+ JOBS_TABLE = None
25
+ METADATA_TABLE = None
26
+ LOGS_TABLE = None
27
+
28
+
29
+ def init(db_uri: str = "", echo=False):
30
+ """Initialise the database.
31
+
32
+ Initialise the sqlalchemy engine, metadata and table objects that we use to
33
+ connect to the database.
34
+
35
+ Create the database and the database tables themselves if they don't
36
+ already exist.
37
+
38
+ :param uri: the sqlalchemy database URI
39
+ :type uri: string
40
+
41
+ :param echo: whether or not to have the sqlalchemy engine log all
42
+ statements to stdout
43
+ :type echo: bool
44
+
45
+ """
46
+ if not db_uri:
47
+ db_uri = toolkit.config.get("ckanext.csvwmapandtransform.db_url")
48
+ global ENGINE, _METADATA, JOBS_TABLE, METADATA_TABLE, LOGS_TABLE
49
+ ENGINE = sqlalchemy.create_engine(db_uri, echo=echo, convert_unicode=True)
50
+ _METADATA = sqlalchemy.MetaData(ENGINE)
51
+ JOBS_TABLE = _init_jobs_table()
52
+ METADATA_TABLE = _init_metadata_table()
53
+ LOGS_TABLE = _init_logs_table()
54
+ _METADATA.create_all(ENGINE)
55
+
56
+
57
+ def drop_all():
58
+ """Delete all the database tables (if they exist).
59
+
60
+ This is for tests to reset the DB. Note that this will delete *all* tables
61
+ in the database, not just tables created by this module (for example
62
+ apscheduler's tables will also be deleted).
63
+
64
+ """
65
+ if _METADATA:
66
+ _METADATA.drop_all(ENGINE)
67
+
68
+
69
+ def delete_job(job_id):
70
+ """Delete a job from the jobs table by job_id.
71
+
72
+ :param job_id: the job_id of the job to be deleted
73
+ :type job_id: unicode
74
+ """
75
+ if job_id:
76
+ job_id = six.text_type(job_id)
77
+
78
+ msg = ""
79
+ with ENGINE.connect() as conn:
80
+ trans = conn.begin()
81
+ try:
82
+ result = conn.execute(
83
+ JOBS_TABLE.delete().where(JOBS_TABLE.c.job_id == job_id)
84
+ )
85
+ if result.rowcount == 0:
86
+ msg = f"No job found with id: {job_id}"
87
+ else:
88
+ msg = f"Job with id: {job_id} has been deleted successfully."
89
+ trans.commit()
90
+ except Exception as e:
91
+ trans.rollback()
92
+ msg = f"An error occurred: {e}"
93
+ return msg
94
+
95
+
96
+ def get_job(job_id):
97
+ """Return the job with the given job_id as a dict."""
98
+ if job_id:
99
+ job_id = six.text_type(job_id)
100
+
101
+ with ENGINE.connect() as conn:
102
+ result = conn.execute(
103
+ JOBS_TABLE.select().where(JOBS_TABLE.c.job_id == job_id)
104
+ ).first()
105
+
106
+ if not result:
107
+ return None
108
+
109
+ result_dict = {
110
+ field: (
111
+ value.isoformat()
112
+ if isinstance(value := getattr(result, field), datetime.datetime)
113
+ else value
114
+ )
115
+ for field in result.keys()
116
+ }
117
+
118
+ result_dict["metadata"] = _get_metadata(job_id)
119
+ result_dict["logs"] = _get_logs(job_id)
120
+
121
+ return result_dict
122
+
123
+
124
+ def add_pending_job(job_id, job_type, data=None, metadata=None, result_url=None):
125
+ """Add a new job with status "pending" to the jobs table."""
126
+ if not data:
127
+ data = {}
128
+ data = six.text_type(json.dumps(data))
129
+
130
+ if job_id:
131
+ job_id = six.text_type(job_id)
132
+ if job_type:
133
+ job_type = six.text_type(job_type)
134
+ if result_url:
135
+ result_url = six.text_type(result_url)
136
+
137
+ if not metadata:
138
+ metadata = {}
139
+
140
+ with ENGINE.connect() as conn:
141
+ trans = conn.begin()
142
+ try:
143
+ conn.execute(
144
+ JOBS_TABLE.insert().values(
145
+ job_id=job_id,
146
+ job_type=job_type,
147
+ status="pending",
148
+ requested_timestamp=datetime.datetime.utcnow(),
149
+ sent_data=data,
150
+ result_url=result_url,
151
+ )
152
+ )
153
+
154
+ inserts = [
155
+ {
156
+ "job_id": job_id,
157
+ "key": six.text_type(key),
158
+ "value": six.text_type(
159
+ json.dumps(value)
160
+ if not isinstance(value, six.string_types)
161
+ else value
162
+ ),
163
+ "type": (
164
+ "json" if not isinstance(value, six.string_types) else "string"
165
+ ),
166
+ }
167
+ for key, value in metadata.items()
168
+ ]
169
+
170
+ if inserts:
171
+ conn.execute(METADATA_TABLE.insert(), inserts)
172
+ trans.commit()
173
+ except Exception:
174
+ trans.rollback()
175
+ raise
176
+
177
+
178
+ class InvalidErrorObjectError(Exception):
179
+ pass
180
+
181
+
182
+ def _validate_error(error):
183
+ """Validate and return the given error object.
184
+
185
+ Based on the given error object, return either None or a dict with a
186
+ "message" key whose value is a string (the dict may also have any other
187
+ keys that it wants).
188
+
189
+ The given "error" object can be:
190
+
191
+ - None, in which case None is returned
192
+
193
+ - A string, in which case a dict like this will be returned:
194
+ {"message": error_string}
195
+
196
+ - A dict with a "message" key whose value is a string, in which case the
197
+ dict will be returned unchanged
198
+
199
+ :param error: the error object to validate
200
+
201
+ :raises InvalidErrorObjectError: If the error object doesn't match any of
202
+ the allowed types
203
+
204
+ """
205
+ if error is None:
206
+ return None
207
+ elif isinstance(error, six.string_types):
208
+ return {"message": error}
209
+ else:
210
+ try:
211
+ message = error["message"]
212
+ if isinstance(message, six.string_types):
213
+ return error
214
+ else:
215
+ raise InvalidErrorObjectError("error['message'] must be a string")
216
+ except (TypeError, KeyError):
217
+ raise InvalidErrorObjectError(
218
+ "error must be either a string or a dict with a message key"
219
+ )
220
+
221
+
222
+ def _update_job(job_id, job_dict):
223
+ """Update the database row for the given job_id with the given job_dict."""
224
+ if job_id:
225
+ job_id = six.text_type(job_id)
226
+
227
+ if "error" in job_dict:
228
+ job_dict["error"] = json.dumps(_validate_error(job_dict["error"]))
229
+ job_dict["error"] = six.text_type(job_dict["error"])
230
+
231
+ if "data" in job_dict:
232
+ job_dict["data"] = six.text_type(job_dict["data"])
233
+
234
+ with ENGINE.connect() as conn:
235
+ conn.execute(
236
+ JOBS_TABLE.update().where(JOBS_TABLE.c.job_id == job_id).values(**job_dict)
237
+ )
238
+
239
+
240
+ def mark_job_as_completed(job_id, data=None):
241
+ """Mark a job as completed successfully.
242
+
243
+ :param job_id: the job_id of the job to be updated
244
+ :type job_id: unicode
245
+
246
+ :param data: the output data returned by the job
247
+ :type data: any JSON-serializable type (including None)
248
+
249
+ """
250
+ update_dict = {
251
+ "status": "complete",
252
+ "data": json.dumps(data),
253
+ "finished_timestamp": datetime.datetime.utcnow(),
254
+ }
255
+ _update_job(job_id, update_dict)
256
+
257
+
258
+ def mark_job_as_missed(job_id):
259
+ """Mark a job as missed because it was in the queue for too long.
260
+
261
+ :param job_id: the job_id of the job to be updated
262
+ :type job_id: unicode
263
+
264
+ """
265
+ update_dict = {
266
+ "status": "error",
267
+ "error": "Job delayed too long, service full",
268
+ "finished_timestamp": datetime.datetime.utcnow(),
269
+ }
270
+ _update_job(job_id, update_dict)
271
+
272
+
273
+ def mark_job_as_errored(job_id, error_object):
274
+ """Mark a job as failed with an error.
275
+
276
+ :param job_id: the job_id of the job to be updated
277
+ :type job_id: unicode
278
+
279
+ :param error_object: the error returned by the job
280
+ :type error_object: either a string or a dict with a "message" key whose
281
+ value is a string
282
+
283
+ """
284
+ update_dict = {
285
+ "status": "error",
286
+ "error": error_object,
287
+ "finished_timestamp": datetime.datetime.utcnow(),
288
+ }
289
+ _update_job(job_id, update_dict)
290
+
291
+
292
+ def mark_job_as_failed_to_post_result(job_id):
293
+ """Mark a job as 'failed to post result'.
294
+
295
+ This happens when a job completes (either successfully or with an error)
296
+ then trying to post the job result back to the job's callback URL fails.
297
+
298
+ FIXME: This overwrites any error from the job itself!
299
+
300
+ :param job_id: the job_id of the job to be updated
301
+ :type job_id: unicode
302
+
303
+ """
304
+ update_dict = {
305
+ "error": "Process completed but unable to post to result_url",
306
+ }
307
+ _update_job(job_id, update_dict)
308
+
309
+
310
+ def _init_jobs_table():
311
+ """Initialise the "jobs" table in the db."""
312
+ _jobs_table = sqlalchemy.Table(
313
+ "jobs",
314
+ _METADATA,
315
+ sqlalchemy.Column("job_id", sqlalchemy.UnicodeText, primary_key=True),
316
+ sqlalchemy.Column("job_type", sqlalchemy.UnicodeText),
317
+ sqlalchemy.Column("status", sqlalchemy.UnicodeText, index=True),
318
+ sqlalchemy.Column("data", sqlalchemy.UnicodeText),
319
+ sqlalchemy.Column("error", sqlalchemy.UnicodeText),
320
+ sqlalchemy.Column("requested_timestamp", sqlalchemy.DateTime),
321
+ sqlalchemy.Column("finished_timestamp", sqlalchemy.DateTime),
322
+ sqlalchemy.Column("sent_data", sqlalchemy.UnicodeText),
323
+ # Callback URL:
324
+ sqlalchemy.Column("result_url", sqlalchemy.UnicodeText),
325
+ )
326
+ return _jobs_table
327
+
328
+
329
+ def _init_metadata_table():
330
+ """Initialise the "metadata" table in the db."""
331
+ _metadata_table = sqlalchemy.Table(
332
+ "metadata",
333
+ _METADATA,
334
+ sqlalchemy.Column(
335
+ "job_id",
336
+ sqlalchemy.ForeignKey("jobs.job_id", ondelete="CASCADE"),
337
+ nullable=False,
338
+ primary_key=True,
339
+ ),
340
+ sqlalchemy.Column("key", sqlalchemy.UnicodeText, primary_key=True),
341
+ sqlalchemy.Column("value", sqlalchemy.UnicodeText, index=True),
342
+ sqlalchemy.Column("type", sqlalchemy.UnicodeText),
343
+ )
344
+ return _metadata_table
345
+
346
+
347
+ def _init_logs_table():
348
+ """Initialise the "logs" table in the db."""
349
+ _logs_table = sqlalchemy.Table(
350
+ "logs",
351
+ _METADATA,
352
+ sqlalchemy.Column(
353
+ "job_id",
354
+ sqlalchemy.ForeignKey("jobs.job_id", ondelete="CASCADE"),
355
+ nullable=False,
356
+ ),
357
+ sqlalchemy.Column("timestamp", sqlalchemy.DateTime),
358
+ sqlalchemy.Column("message", sqlalchemy.UnicodeText),
359
+ sqlalchemy.Column("level", sqlalchemy.UnicodeText),
360
+ sqlalchemy.Column("module", sqlalchemy.UnicodeText),
361
+ sqlalchemy.Column("funcName", sqlalchemy.UnicodeText),
362
+ sqlalchemy.Column("lineno", sqlalchemy.Integer),
363
+ )
364
+ return _logs_table
365
+
366
+
367
+ def _get_metadata(job_id):
368
+ """Return any metadata for the given job_id from the metadata table."""
369
+ job_id = six.text_type(job_id)
370
+
371
+ with ENGINE.connect() as conn:
372
+ results = conn.execute(
373
+ METADATA_TABLE.select().where(METADATA_TABLE.c.job_id == job_id)
374
+ ).fetchall()
375
+
376
+ metadata = {
377
+ row["key"]: json.loads(row["value"]) if row["type"] == "json" else row["value"]
378
+ for row in results
379
+ }
380
+ return metadata
381
+
382
+
383
+ def _get_logs(job_id):
384
+ """Return any logs for the given job_id from the logs table."""
385
+ job_id = six.text_type(job_id)
386
+
387
+ with ENGINE.connect() as conn:
388
+ results = conn.execute(
389
+ LOGS_TABLE.select().where(LOGS_TABLE.c.job_id == job_id)
390
+ ).fetchall()
391
+
392
+ results = [dict(result) for result in results]
393
+
394
+ for result in results:
395
+ result.pop("job_id")
396
+
397
+ return results
@@ -0,0 +1,67 @@
1
+ # encoding: utf-8
2
+
3
+ import re
4
+ from typing import Any
5
+
6
+ import ckan.plugins.toolkit as toolkit
7
+ import requests
8
+
9
+ log = __import__("logging").getLogger(__name__)
10
+
11
+
12
+ def csvwmapandtransform__status_description(status: dict[str, Any]):
13
+ _ = toolkit._
14
+
15
+ if status.get("status"):
16
+ captions = {
17
+ "complete": _("Complete"),
18
+ "pending": _("Pending"),
19
+ "submitting": _("Submitting"),
20
+ "error": _("Error"),
21
+ }
22
+
23
+ return captions.get(status["status"], status["status"].capitalize())
24
+ else:
25
+ return _("Not Uploaded Yet")
26
+
27
+
28
+ def common_member(a, b):
29
+ return any(i in b for i in a)
30
+
31
+
32
+ def csvwmapandtransform_show_tools(resource):
33
+ formats = toolkit.config.get("ckanext.csvwmapandtransform.formats")
34
+
35
+ format_parts = re.split("/|;", resource["format"].lower().replace(" ", ""))
36
+ if common_member(format_parts, formats):
37
+ return True
38
+ else:
39
+ False
40
+
41
+
42
+ def csvwmapandtransform_service_available():
43
+ url = toolkit.config.get("ckanext.csvwmapandtransform.maptomethod_url")
44
+ ssl_verify = toolkit.config.get("ckanext.csvwmapandtransform.ssl_verify")
45
+ # log.debug(f"mapomethodurl: {url} {bool(url)}")
46
+ if not url:
47
+ return False # If EXTRACT_URL is not set, return False
48
+ try:
49
+ # Perform a HEAD request (lightweight check) to see if the service responds
50
+ response = requests.head(url, timeout=5, verify=ssl_verify)
51
+ # log.debug(f"reponse: {response}")
52
+ if (200 <= response.status_code < 400) or response.status_code == 405:
53
+ return True # URL is reachable and returns a valid status code
54
+ else:
55
+ return False # URL is reachable but response status is not valid
56
+ except requests.RequestException as e:
57
+ # If there's any issue (timeout, connection error, etc.)
58
+ # log.debug(e)
59
+ return False
60
+
61
+
62
+ def get_helpers():
63
+ return {
64
+ "csvwmapandtransform__status_description": csvwmapandtransform__status_description,
65
+ "csvwmapandtransform_show_tools": csvwmapandtransform_show_tools,
66
+ "csvwmapandtransform_service_available": csvwmapandtransform_service_available,
67
+ }
@@ -0,0 +1,92 @@
1
+ import json
2
+
3
+ import ckan.plugins.toolkit as toolkit
4
+ import requests
5
+
6
+ log = __import__("logging").getLogger(__name__)
7
+
8
+
9
+ def post_request(url, headers, data, files=None):
10
+ ssl_verify = toolkit.config.get("ckanext.csvwmapandtransform.ssl_verify")
11
+ if not ssl_verify:
12
+ requests.packages.urllib3.disable_warnings()
13
+
14
+ try:
15
+ if files:
16
+ # should crate a multipart form upload
17
+ response = requests.post(
18
+ url, data=data, headers=headers, files=files, verify=ssl_verify
19
+ )
20
+ else:
21
+ # a application json post request
22
+ response = requests.post(
23
+ url, data=json.dumps(data), headers=headers, verify=ssl_verify
24
+ )
25
+ response.raise_for_status()
26
+
27
+ except Exception as e:
28
+ # placeholder for save file / clean-up
29
+ log.error(e)
30
+ return None
31
+ # raise SystemExit(e) from None
32
+ return response
33
+
34
+
35
+ def check_mapping(map_url: str, data_url: str, authorization: None):
36
+ rdfconverter_url = toolkit.config.get(
37
+ "ckanext.csvwmapandtransform.rdfconverter_url"
38
+ )
39
+ log.debug("checking mapping at: {} with data url: {}".format(map_url, data_url))
40
+ # curl -X 'POST' 'http://docker-dev.iwm.fraunhofer.de:5003/api/checkmapping' -H 'accept: application/json' -H 'Content-Type: application/json' -d '{"data_url": "https://raw.githubusercontent.com/Mat-O-Lab/CSVToCSVW/main/examples/example-metadata.json", "mapping_url": "https://github.com/Mat-O-Lab/MapToMethod/raw/main/examples/example-map.yaml"}'
41
+ url = rdfconverter_url + "/api/checkmapping"
42
+ log.debug("rdf converter api call: {}".format(url))
43
+ data = {"mapping_url": map_url, "data_url": data_url}
44
+ headers = {"Content-Type": "application/json"}
45
+ if authorization:
46
+ headers["Authorization"] = authorization
47
+ r = post_request(url, headers, data)
48
+ # r=requests.get(rdfconverter_url+"/info")
49
+ # log.debug(r)
50
+ if r and r.status_code == 200:
51
+ res = r.json()
52
+ log.debug("map check results: {}".format(res))
53
+ return res
54
+ else:
55
+ log.debug("map check error: {}".format(r))
56
+ return None
57
+
58
+
59
+ def get_joined_rdf(map_url: str, data_url: str, authorization: None):
60
+ log.debug("createing joined rdf: {} with data url: {}".format(map_url, data_url))
61
+ rdfconverter_url = toolkit.config.get(
62
+ "ckanext.csvwmapandtransform.rdfconverter_url"
63
+ )
64
+ url = rdfconverter_url + "/api/createrdf?return_type=turtle"
65
+ data = {"mapping_url": map_url, "data_url": data_url}
66
+ headers = {"Content-type": "application/json", "Accept": "application/json"}
67
+ if authorization:
68
+ headers["Authorization"] = authorization
69
+ log.debug(f"headers: {headers}")
70
+
71
+ r = post_request(url, headers, data)
72
+ if r and r.status_code == 200:
73
+ r = r.json()
74
+ filename = r["filename"]
75
+ print(
76
+ "applied {} mapping rules and skipped {}".format(
77
+ r["num_mappings_applied"], r["num_mappings_skipped"]
78
+ )
79
+ )
80
+ return (
81
+ filename,
82
+ r["graph"],
83
+ r["num_mappings_applied"],
84
+ r["num_mappings_skipped"],
85
+ )
86
+ else:
87
+ return (
88
+ None,
89
+ None,
90
+ None,
91
+ None,
92
+ )
@@ -0,0 +1,140 @@
1
+ import os
2
+ import re
3
+
4
+ import ckan.plugins as plugins
5
+ import ckan.plugins.toolkit as toolkit
6
+ from ckan import model
7
+ from ckan.config.declaration import Declaration, Key
8
+ from ckan.lib.plugins import DefaultTranslation
9
+
10
+ if toolkit.check_ckan_version("2.10"):
11
+ from ckan.types import Context
12
+ else:
13
+
14
+ class Context(dict):
15
+ def __init__(self, **kwargs):
16
+ super().__init__(**kwargs)
17
+
18
+
19
+ from typing import Any
20
+
21
+ from ckanext.csvwmapandtransform import action, auth, helpers, views
22
+
23
+ log = __import__("logging").getLogger(__name__)
24
+
25
+
26
+ class CsvwMapAndTransformPlugin(plugins.SingletonPlugin, DefaultTranslation):
27
+ plugins.implements(plugins.ITranslation)
28
+ plugins.implements(plugins.IConfigurer)
29
+ plugins.implements(plugins.IConfigDeclaration)
30
+ plugins.implements(plugins.ITemplateHelpers)
31
+ plugins.implements(plugins.IResourceUrlChange)
32
+ plugins.implements(plugins.IResourceController, inherit=True)
33
+ plugins.implements(plugins.IActions)
34
+ plugins.implements(plugins.IAuthFunctions)
35
+ plugins.implements(plugins.IBlueprint)
36
+
37
+ # IConfigurer
38
+
39
+ def update_config(self, config_):
40
+ toolkit.add_template_directory(config_, "templates")
41
+ toolkit.add_public_directory(config_, "public")
42
+ toolkit.add_resource("assets", "csvwmapandtransform")
43
+
44
+ # IConfigDeclaration
45
+
46
+ def declare_config_options(self, declaration: Declaration, key: Key):
47
+
48
+ declaration.annotate("csvwmapandtransform")
49
+ group = key.ckanext.csvwmapandtransform
50
+ declaration.declare_bool(group.ssl_verify, True)
51
+ declaration.declare(group.db_url, plugins.toolkit.config.get("sqlalchemy.url"))
52
+ declaration.declare(group.maptomethod_url, "https://maptomethod.matolab.org")
53
+ declaration.declare(group.rdfconverter_url, "https://rdfconverter.matolab.org")
54
+ declaration.declare(group.ckan_token, "")
55
+ declaration.declare(
56
+ group.formats, "json json-ld turtle n3 nt hext trig longturtle xml ld+json"
57
+ )
58
+
59
+ # IResourceUrlChange
60
+
61
+ def notify(self, resource: model.Resource):
62
+ context: Context = {"ignore_auth": True}
63
+ resource_dict = toolkit.get_action("resource_show")(
64
+ context,
65
+ {
66
+ "id": resource.id,
67
+ },
68
+ )
69
+ self._sumbit_transform(resource_dict)
70
+
71
+ # IResourceController
72
+
73
+ if not toolkit.check_ckan_version("2.10") or toolkit.check_ckan_version("2.11"):
74
+
75
+ def after_create(self, context, resource_dict):
76
+ self.after_resource_create(context, resource_dict)
77
+
78
+ # def before_show(self, resource_dict):
79
+ # self.before_resource_show(resource_dict)
80
+
81
+ def after_update(self, context: Context, resource_dict: dict[str, Any]):
82
+ self._sumbit_transform(resource_dict)
83
+
84
+ def after_resource_create(self, context: Context, resource_dict: dict[str, Any]):
85
+ self._sumbit_transform(resource_dict)
86
+
87
+ def _sumbit_transform(self, resource_dict: dict[str, Any]):
88
+ context = {"model": model, "ignore_auth": True, "defer_commit": True}
89
+ formats = toolkit.config.get("ckanext.csvwmapandtransform.formats")
90
+ format = resource_dict.get("format", None)
91
+ submit = (
92
+ format
93
+ and format.lower() in formats
94
+ and "-joined" not in resource_dict["url"]
95
+ )
96
+ log.debug(
97
+ "Submitting resource {0} with format {1}".format(
98
+ resource_dict["id"], format
99
+ )
100
+ + " to csvwmapandtransform_transform"
101
+ )
102
+
103
+ if not submit:
104
+ return
105
+
106
+ try:
107
+ log.debug(
108
+ "Submitting resource {0}".format(resource_dict["id"])
109
+ + " to csvwmapandtransform_transform"
110
+ )
111
+ toolkit.get_action("csvwmapandtransform_transform")(
112
+ context, {"id": resource_dict["id"]}
113
+ )
114
+
115
+ except toolkit.ValidationError as e:
116
+ # If RDFConverter is offline want to catch error instead
117
+ # of raising otherwise resource save will fail with 500
118
+ log.critical(e)
119
+ pass
120
+
121
+ # ITemplateHelpers
122
+
123
+ def get_helpers(self):
124
+ return helpers.get_helpers()
125
+
126
+ # IActions
127
+
128
+ def get_actions(self):
129
+ actions = action.get_actions()
130
+ return actions
131
+
132
+ # IBlueprint
133
+
134
+ def get_blueprint(self):
135
+ return views.get_blueprint()
136
+
137
+ # IAuthFunctions
138
+
139
+ def get_auth_functions(self):
140
+ return auth.get_auth_functions()