Skip to content

Commit a079005

Browse files
committed
Feat (urls): Interface allowing to customize dataset, resource and organization uri
1 parent dd3b1e8 commit a079005

5 files changed

Lines changed: 267 additions & 2 deletions

File tree

ckanext/dcat/interfaces.py

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,74 @@
11
from ckan.plugins.interfaces import Interface
22

33

4+
class IDCATURIGenerator(Interface):
5+
'''
6+
Interface for customizing URI generation in DCAT serializations
7+
'''
8+
9+
def catalog_uri(self, default_uri):
10+
'''
11+
Called when generating the catalog URI for RDF serializations.
12+
13+
Allows plugins to customize how the catalog URI is generated.
14+
15+
:param default_uri: The default catalog URI generated by the system
16+
:type default_uri: string
17+
18+
:returns: The catalog URI to use. If None, the default will be used.
19+
:rtype: string or None
20+
'''
21+
return default_uri
22+
23+
def dataset_uri(self, dataset_dict, default_uri):
24+
'''
25+
Called when generating the dataset URI for RDF serializations.
26+
27+
Allows plugins to customize how dataset URIs are generated.
28+
29+
:param dataset_dict: The dataset dictionary
30+
:type dataset_dict: dict
31+
:param default_uri: The default dataset URI generated by the system
32+
:type default_uri: string
33+
34+
:returns: The dataset URI to use. If None, the default will be used.
35+
:rtype: string or None
36+
'''
37+
return default_uri
38+
39+
def resource_uri(self, resource_dict, default_uri):
40+
'''
41+
Called when generating the resource URI for RDF serializations.
42+
43+
Allows plugins to customize how resource URIs are generated.
44+
45+
:param resource_dict: The resource dictionary
46+
:type resource_dict: dict
47+
:param default_uri: The default resource URI generated by the system
48+
:type default_uri: string
49+
50+
:returns: The resource URI to use. If None, the default will be used.
51+
:rtype: string or None
52+
'''
53+
return default_uri
54+
55+
def publisher_uri(self, dataset_dict, default_uri):
56+
'''
57+
Called when generating the publisher URI for RDF serializations.
58+
59+
Allows plugins to customize how publisher URIs are generated.
60+
61+
:param dataset_dict: The dataset dictionary
62+
:type dataset_dict: dict
63+
:param default_uri: The default publisher URI generated by the system
64+
:type default_uri: string
65+
66+
:returns: The publisher URI to use. If None, the default will be used.
67+
:rtype: string or None
68+
'''
69+
return default_uri
70+
71+
472
class IDCATRDFHarvester(Interface):
573

674
def before_download(self, url, harvest_job):

ckanext/dcat/utils.py

Lines changed: 33 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,10 @@
1313

1414
from ckan import model
1515
import ckan.plugins.toolkit as toolkit
16+
import ckan.plugins as plugins
1617

1718
from ckanext.dcat.exceptions import RDFProfileException
19+
from ckanext.dcat.interfaces import IDCATURIGenerator
1820

1921
from ckan.views.home import index as index_endpoint
2022
from ckan.views.dataset import read as read_endpoint
@@ -128,6 +130,13 @@ def catalog_uri():
128130
'the `ckanext.dcat.base_uri` or `ckan.site_url` ' +
129131
'option')
130132

133+
# Allow plugins to modify the catalog URI
134+
for plugin in plugins.PluginImplementations(IDCATURIGenerator):
135+
result = plugin.catalog_uri(uri)
136+
if result is not None:
137+
uri = result
138+
break
139+
131140
return uri
132141

133142

@@ -164,6 +173,13 @@ def dataset_uri(dataset_dict):
164173
str(uuid.uuid4()))
165174
log.warning('Using a random id for dataset URI')
166175

176+
# Allow plugins to modify the dataset URI
177+
for plugin in plugins.PluginImplementations(IDCATURIGenerator):
178+
result = plugin.dataset_uri(dataset_dict, uri)
179+
if result is not None:
180+
uri = result
181+
break
182+
167183
return uri
168184

169185

@@ -194,6 +210,13 @@ def resource_uri(resource_dict):
194210
dataset_id,
195211
resource_dict['id'])
196212

213+
# Allow plugins to modify the resource URI
214+
for plugin in plugins.PluginImplementations(IDCATURIGenerator):
215+
result = plugin.resource_uri(resource_dict, uri)
216+
if result is not None:
217+
uri = result
218+
break
219+
197220
return uri
198221

199222

@@ -208,11 +231,19 @@ def publisher_uri_organization_fallback(dataset_dict):
208231
Returns a string with the publisher URI, or None if no URI could be
209232
generated.
210233
'''
234+
uri = None
211235
if dataset_dict.get('organization'):
212-
return '{0}/organization/{1}'.format(catalog_uri().rstrip('/'),
236+
uri = '{0}/organization/{1}'.format(catalog_uri().rstrip('/'),
213237
dataset_dict['organization']['id'])
214238

215-
return None
239+
# Allow plugins to modify the publisher or organization URI
240+
for plugin in plugins.PluginImplementations(IDCATURIGenerator):
241+
result = plugin.publisher_uri(dataset_dict, uri)
242+
if result is not None:
243+
uri = result
244+
break
245+
246+
return uri
216247

217248
def dataset_id_from_resource(resource_dict):
218249
'''

docs/index.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -119,3 +119,5 @@ These are implemented internally using:
119119
* An [RDF Parser](writing-profiles.md#rdf-dcat-parser) that allows to read RDF serializations in different formats and extract CKAN dataset dicts, using customizable [profiles](profiles.md#profiles).
120120

121121
* An [RDF Serializer](writing-profiles.md#rdf-dcat-serializer) that allows to transform CKAN datasets metadata to different semantic formats, also allowing customizable [profiles](profiles.md#profiles).
122+
123+
* [URI Customization Interface](uri-customization.md) that allows plugins to customize how URIs are generated for catalogs, datasets, resources, and publishers in RDF serializations.

docs/uri-customization.md

Lines changed: 163 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,163 @@
1+
# URI Customization Interface
2+
3+
The DCAT extension provides an interface (`IDCATURIGenerator`) that allows other plugins to customize how URIs are generated for RDF serializations. This is useful when you need to:
4+
5+
- Use custom URI patterns for dataset, resources, and organizations. This is useful when you have decoupled frontend running in different domains or need to follow specific URI schemes.
6+
- Integrate with external authority systems
7+
- Use persistent identifiers like DOIs or handles etc.
8+
9+
## Interface Methods
10+
11+
The `IDCATURIGenerator` interface provides four methods that can be implemented:
12+
13+
### `catalog_uri(default_uri)`
14+
15+
Customize the catalog URI used to identify the entire CKAN instance.
16+
17+
**Parameters:**
18+
- `default_uri` (string): The default catalog URI generated by the system
19+
20+
**Returns:**
21+
- `string` or `None`: The catalog URI to use, or None to use the default
22+
23+
**Example:**
24+
```python
25+
def catalog_uri(self, default_uri):
26+
custom_domain = toolkit.config.get('ckanext.my_extension.catalog_domain')
27+
if custom_domain:
28+
return f"https://{custom_domain}/{default_uri}"
29+
return None
30+
```
31+
32+
### `dataset_uri(dataset_dict, default_uri)`
33+
34+
Customize the dataset URI used to identify individual datasets.
35+
36+
**Parameters:**
37+
- `dataset_dict` (dict): The dataset dictionary containing metadata
38+
- `default_uri` (string): The default dataset URI generated by the system
39+
40+
**Returns:**
41+
- `string` or `None`: The dataset URI to use, or None to use the default
42+
43+
**Example:**
44+
```python
45+
def dataset_uri(self, dataset_dict, default_uri):
46+
# Use DOI if available
47+
doi = dataset_dict.get('doi')
48+
if doi:
49+
return f"https://doi.org/{doi}"
50+
return None # Use default
51+
```
52+
53+
### `resource_uri(resource_dict, default_uri)`
54+
55+
Customize the resource URI used to identify individual resources.
56+
57+
**Parameters:**
58+
- `resource_dict` (dict): The resource dictionary containing metadata
59+
- `default_uri` (string): The default resource URI generated by the system
60+
61+
**Returns:**
62+
- `string` or `None`: The resource URI to use, or None to use the default
63+
64+
**Example:**
65+
```python
66+
def resource_uri(self, resource_dict, default_uri):
67+
# Use the actual URL for API resources
68+
resource_format = resource_dict.get('format', '').lower()
69+
if resource_format == 'api':
70+
return resource_dict.get('url')
71+
return None # Use default
72+
```
73+
74+
### `publisher_uri(dataset_dict, default_uri)`
75+
76+
Customize the publisher URI used to identify the organization that published the dataset.
77+
78+
**Parameters:**
79+
- `dataset_dict` (dict): The dataset dictionary containing metadata
80+
- `default_uri` (string): The default publisher URI generated by the system
81+
82+
**Returns:**
83+
- `string` or `None`: The publisher URI to use, or None to use the default
84+
85+
**Example:**
86+
```python
87+
def publisher_uri(self, dataset_dict, default_uri):
88+
organization = dataset_dict.get('organization', {})
89+
# Use ROR ID if available
90+
ror_id = organization.get('ror_id')
91+
if ror_id:
92+
return f"https://ror.org/{ror_id}"
93+
return None # Use default
94+
```
95+
96+
## Implementation Example
97+
98+
Here's a complete example of a plugin that implements the `IDCATURIGenerator` interface:
99+
100+
```python
101+
import ckan.plugins as plugins
102+
import ckan.plugins.toolkit as toolkit
103+
from ckanext.dcat.interfaces import IDCATURIGenerator
104+
105+
class MyURIPlugin(plugins.SingletonPlugin):
106+
plugins.implements(plugins.IConfigurer)
107+
plugins.implements(IDCATURIGenerator)
108+
109+
# IConfigurer
110+
def update_config(self, config_):
111+
pass
112+
113+
# IDCATURIGenerator
114+
def catalog_uri(self, default_uri):
115+
# Add version to catalog URI
116+
version = toolkit.config.get('my_extension.catalog_version', 'v1')
117+
if default_uri:
118+
return f"{default_uri.rstrip('/')}/{version}"
119+
return None
120+
121+
def dataset_uri(self, dataset_dict, default_uri):
122+
# Use custom identifier if available
123+
custom_id = dataset_dict.get('custom_identifier')
124+
if custom_id:
125+
base_uri = toolkit.config.get('ckanext.dcat.base_uri')
126+
return f"{base_uri}/data/{custom_id}"
127+
return None
128+
129+
def resource_uri(self, resource_dict, default_uri):
130+
# Use content hash for stable URIs
131+
content_hash = resource_dict.get('content_hash')
132+
if content_hash:
133+
base_uri = toolkit.config.get('ckanext.dcat.base_uri')
134+
return f"{base_uri}/resource/{content_hash}"
135+
return None
136+
137+
def publisher_uri(self, dataset_dict, default_uri):
138+
organization = dataset_dict.get('organization', {})
139+
# Use external authority URI
140+
authority_id = organization.get('authority_id')
141+
if authority_id:
142+
return f"https://authority.example.org/org/{authority_id}"
143+
return None
144+
```
145+
146+
## Configuration
147+
148+
You can add configuration options to your plugin to make URI generation configurable:
149+
150+
```ini
151+
# In your CKAN configuration file
152+
my_extension.catalog_version = v2
153+
my_extension.use_dois = true
154+
my_extension.authority_base_uri = https://authority.example.org
155+
```
156+
157+
## Multiple Plugins
158+
159+
If multiple plugins implement the `IDCATURIGenerator` interface, only the first plugin that returns a non-None value will be used for each URI type. Plugins are called in the order they are loaded.
160+
161+
## Backward Compatibility
162+
163+
This interface is completely backward compatible. Existing installations will continue to work unchanged, and the interface only affects URI generation when plugins explicitly implement it.

mkdocs.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,7 @@ nav:
7878
- 'harvester.md'
7979
- Google Dataset Search: 'google-dataset-search.md'
8080
- Croissant ML: 'croissant.md'
81+
- URI Customization: 'uri-customization.md'
8182
- CLI: 'cli.md'
8283
- Configuration reference: 'configuration.md'
8384
- Contributing: 'contributing.md'

0 commit comments

Comments
 (0)