ckanapi-harvesters 0.0.0__py3-none-any.whl → 0.0.2__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.
- ckanapi_harvesters/__init__.py +32 -10
- ckanapi_harvesters/auxiliary/__init__.py +26 -0
- ckanapi_harvesters/auxiliary/ckan_action.py +93 -0
- ckanapi_harvesters/auxiliary/ckan_api_key.py +213 -0
- ckanapi_harvesters/auxiliary/ckan_auxiliary.py +293 -0
- ckanapi_harvesters/auxiliary/ckan_configuration.py +50 -0
- ckanapi_harvesters/auxiliary/ckan_defs.py +10 -0
- ckanapi_harvesters/auxiliary/ckan_errors.py +129 -0
- ckanapi_harvesters/auxiliary/ckan_map.py +509 -0
- ckanapi_harvesters/auxiliary/ckan_model.py +992 -0
- ckanapi_harvesters/auxiliary/ckan_vocabulary_deprecated.py +104 -0
- ckanapi_harvesters/auxiliary/deprecated.py +82 -0
- ckanapi_harvesters/auxiliary/error_level_message.py +51 -0
- ckanapi_harvesters/auxiliary/external_code_import.py +98 -0
- ckanapi_harvesters/auxiliary/list_records.py +60 -0
- ckanapi_harvesters/auxiliary/login.py +163 -0
- ckanapi_harvesters/auxiliary/path.py +208 -0
- ckanapi_harvesters/auxiliary/proxy_config.py +298 -0
- ckanapi_harvesters/auxiliary/urls.py +40 -0
- ckanapi_harvesters/builder/__init__.py +40 -0
- ckanapi_harvesters/builder/builder_aux.py +20 -0
- ckanapi_harvesters/builder/builder_ckan.py +238 -0
- ckanapi_harvesters/builder/builder_errors.py +36 -0
- ckanapi_harvesters/builder/builder_field.py +122 -0
- ckanapi_harvesters/builder/builder_package.py +9 -0
- ckanapi_harvesters/builder/builder_package_1_basic.py +1291 -0
- ckanapi_harvesters/builder/builder_package_2_harvesters.py +40 -0
- ckanapi_harvesters/builder/builder_package_3_multi_threaded.py +45 -0
- ckanapi_harvesters/builder/builder_package_example.xlsx +0 -0
- ckanapi_harvesters/builder/builder_resource.py +589 -0
- ckanapi_harvesters/builder/builder_resource_datastore.py +561 -0
- ckanapi_harvesters/builder/builder_resource_datastore_multi_abc.py +367 -0
- ckanapi_harvesters/builder/builder_resource_datastore_multi_folder.py +273 -0
- ckanapi_harvesters/builder/builder_resource_datastore_multi_harvester.py +278 -0
- ckanapi_harvesters/builder/builder_resource_datastore_unmanaged.py +145 -0
- ckanapi_harvesters/builder/builder_resource_datastore_url.py +150 -0
- ckanapi_harvesters/builder/builder_resource_init.py +126 -0
- ckanapi_harvesters/builder/builder_resource_multi_abc.py +361 -0
- ckanapi_harvesters/builder/builder_resource_multi_datastore.py +146 -0
- ckanapi_harvesters/builder/builder_resource_multi_file.py +505 -0
- ckanapi_harvesters/builder/example/__init__.py +21 -0
- ckanapi_harvesters/builder/example/builder_example.py +21 -0
- ckanapi_harvesters/builder/example/builder_example_aux_fun.py +24 -0
- ckanapi_harvesters/builder/example/builder_example_download.py +44 -0
- ckanapi_harvesters/builder/example/builder_example_generate_data.py +73 -0
- ckanapi_harvesters/builder/example/builder_example_patch_upload.py +51 -0
- ckanapi_harvesters/builder/example/builder_example_policy.py +114 -0
- ckanapi_harvesters/builder/example/builder_example_test_sql.py +53 -0
- ckanapi_harvesters/builder/example/builder_example_tests.py +87 -0
- ckanapi_harvesters/builder/example/builder_example_tests_offline.py +57 -0
- ckanapi_harvesters/builder/example/package/ckan-dpg.svg +74 -0
- ckanapi_harvesters/builder/example/package/users_local.csv +3 -0
- ckanapi_harvesters/builder/mapper_datastore.py +93 -0
- ckanapi_harvesters/builder/mapper_datastore_multi.py +262 -0
- ckanapi_harvesters/builder/specific/__init__.py +11 -0
- ckanapi_harvesters/builder/specific/configuration_builder.py +66 -0
- ckanapi_harvesters/builder/specific_builder_abc.py +23 -0
- ckanapi_harvesters/ckan_api/__init__.py +20 -0
- ckanapi_harvesters/ckan_api/ckan_api.py +11 -0
- ckanapi_harvesters/ckan_api/ckan_api_0_base.py +896 -0
- ckanapi_harvesters/ckan_api/ckan_api_1_map.py +1028 -0
- ckanapi_harvesters/ckan_api/ckan_api_2_readonly.py +934 -0
- ckanapi_harvesters/ckan_api/ckan_api_3_policy.py +229 -0
- ckanapi_harvesters/ckan_api/ckan_api_4_readwrite.py +579 -0
- ckanapi_harvesters/ckan_api/ckan_api_5_manage.py +1225 -0
- ckanapi_harvesters/ckan_api/ckan_api_params.py +192 -0
- ckanapi_harvesters/ckan_api/deprecated/__init__.py +9 -0
- ckanapi_harvesters/ckan_api/deprecated/ckan_api_deprecated.py +267 -0
- ckanapi_harvesters/ckan_api/deprecated/ckan_api_deprecated_vocabularies.py +189 -0
- ckanapi_harvesters/harvesters/__init__.py +23 -0
- ckanapi_harvesters/harvesters/data_cleaner/__init__.py +17 -0
- ckanapi_harvesters/harvesters/data_cleaner/data_cleaner_abc.py +240 -0
- ckanapi_harvesters/harvesters/data_cleaner/data_cleaner_errors.py +23 -0
- ckanapi_harvesters/harvesters/data_cleaner/data_cleaner_upload.py +9 -0
- ckanapi_harvesters/harvesters/data_cleaner/data_cleaner_upload_1_basic.py +430 -0
- ckanapi_harvesters/harvesters/data_cleaner/data_cleaner_upload_2_geom.py +98 -0
- ckanapi_harvesters/harvesters/file_formats/__init__.py +10 -0
- ckanapi_harvesters/harvesters/file_formats/csv_format.py +43 -0
- ckanapi_harvesters/harvesters/file_formats/file_format_abc.py +39 -0
- ckanapi_harvesters/harvesters/file_formats/file_format_init.py +25 -0
- ckanapi_harvesters/harvesters/file_formats/shp_format.py +129 -0
- ckanapi_harvesters/harvesters/harvester_abc.py +190 -0
- ckanapi_harvesters/harvesters/harvester_errors.py +31 -0
- ckanapi_harvesters/harvesters/harvester_init.py +30 -0
- ckanapi_harvesters/harvesters/harvester_model.py +49 -0
- ckanapi_harvesters/harvesters/harvester_params.py +323 -0
- ckanapi_harvesters/harvesters/postgre_harvester.py +495 -0
- ckanapi_harvesters/harvesters/postgre_params.py +86 -0
- ckanapi_harvesters/harvesters/pymongo_data_cleaner.py +173 -0
- ckanapi_harvesters/harvesters/pymongo_harvester.py +355 -0
- ckanapi_harvesters/harvesters/pymongo_params.py +54 -0
- ckanapi_harvesters/policies/__init__.py +20 -0
- ckanapi_harvesters/policies/data_format_policy.py +269 -0
- ckanapi_harvesters/policies/data_format_policy_abc.py +97 -0
- ckanapi_harvesters/policies/data_format_policy_custom_fields.py +156 -0
- ckanapi_harvesters/policies/data_format_policy_defs.py +135 -0
- ckanapi_harvesters/policies/data_format_policy_errors.py +79 -0
- ckanapi_harvesters/policies/data_format_policy_lists.py +234 -0
- ckanapi_harvesters/policies/data_format_policy_tag_groups.py +35 -0
- ckanapi_harvesters/reports/__init__.py +11 -0
- ckanapi_harvesters/reports/admin_report.py +292 -0
- {ckanapi_harvesters-0.0.0.dist-info → ckanapi_harvesters-0.0.2.dist-info}/METADATA +74 -38
- ckanapi_harvesters-0.0.2.dist-info/RECORD +105 -0
- ckanapi_harvesters/divider/__init__.py +0 -27
- ckanapi_harvesters/divider/divider.py +0 -53
- ckanapi_harvesters/divider/divider_error.py +0 -59
- ckanapi_harvesters/main.py +0 -30
- ckanapi_harvesters-0.0.0.dist-info/RECORD +0 -9
- {ckanapi_harvesters-0.0.0.dist-info → ckanapi_harvesters-0.0.2.dist-info}/WHEEL +0 -0
- {ckanapi_harvesters-0.0.0.dist-info → ckanapi_harvesters-0.0.2.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,238 @@
|
|
|
1
|
+
#!python3
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
"""
|
|
4
|
+
Code to upload metadata to the CKAN server to create/update an existing package
|
|
5
|
+
The metadata is defined by the user in an Excel worksheet
|
|
6
|
+
This file implements the ckan connection definition.
|
|
7
|
+
"""
|
|
8
|
+
from typing import Union
|
|
9
|
+
import os
|
|
10
|
+
import json
|
|
11
|
+
|
|
12
|
+
import pandas as pd
|
|
13
|
+
|
|
14
|
+
from ckanapi_harvesters.ckan_api import CkanApi
|
|
15
|
+
from ckanapi_harvesters.auxiliary.ckan_defs import environ_keyword
|
|
16
|
+
from ckanapi_harvesters.auxiliary.path import make_path_relative, path_rel_to_dir
|
|
17
|
+
from ckanapi_harvesters.auxiliary.ckan_auxiliary import _string_from_element
|
|
18
|
+
from ckanapi_harvesters.auxiliary.ckan_auxiliary import ca_file_rel_to_dir, ca_arg_to_str
|
|
19
|
+
from ckanapi_harvesters.auxiliary.proxy_config import ProxyConfig
|
|
20
|
+
from ckanapi_harvesters.policies.data_format_policy import CkanPackageDataFormatPolicy
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class BuilderCkan:
|
|
24
|
+
def __init__(self, url:str=None, apikey_file:str=None, proxy:ProxyConfig=None):
|
|
25
|
+
if proxy is None:
|
|
26
|
+
proxy = ProxyConfig()
|
|
27
|
+
self.url: str = url
|
|
28
|
+
self.apikey_file: str = apikey_file
|
|
29
|
+
self._proxy_config: ProxyConfig = proxy
|
|
30
|
+
self._policy_file: Union[str, None] = None
|
|
31
|
+
self._policy: Union[CkanPackageDataFormatPolicy, None] = None
|
|
32
|
+
self.ckan_ca: Union[bool, str, None] = None
|
|
33
|
+
self.extern_ca: Union[bool, str, None] = None
|
|
34
|
+
self._ckan_ca_src: Union[str, None] = None
|
|
35
|
+
self._extern_ca_src: Union[str, None] = None
|
|
36
|
+
self.options_string: Union[str, None] = None
|
|
37
|
+
self.comment: Union[str, None] = None
|
|
38
|
+
|
|
39
|
+
def __str__(self):
|
|
40
|
+
return f"CKAN builder"
|
|
41
|
+
|
|
42
|
+
def __copy__(self):
|
|
43
|
+
return self.copy()
|
|
44
|
+
|
|
45
|
+
def copy(self) -> "BuilderCkan":
|
|
46
|
+
dest = BuilderCkan()
|
|
47
|
+
dest.url = self.url
|
|
48
|
+
dest.apikey_file = self.apikey_file
|
|
49
|
+
dest._proxy_config = self._proxy_config.copy()
|
|
50
|
+
dest._policy_file = self._policy_file
|
|
51
|
+
if self._policy is not None:
|
|
52
|
+
dest._policy = self._policy.copy()
|
|
53
|
+
dest.ckan_ca = self.ckan_ca
|
|
54
|
+
dest.extern_ca = self.extern_ca
|
|
55
|
+
dest._ckan_ca_src = self._ckan_ca_src
|
|
56
|
+
dest._extern_ca_src = self._extern_ca_src
|
|
57
|
+
dest.options_string = self.options_string
|
|
58
|
+
dest.comment = self.comment
|
|
59
|
+
return dest
|
|
60
|
+
|
|
61
|
+
@property
|
|
62
|
+
def proxies(self) -> dict:
|
|
63
|
+
return self._proxy_config.proxies
|
|
64
|
+
@proxies.setter
|
|
65
|
+
def proxies(self, proxies:dict) -> None:
|
|
66
|
+
self._proxy_config.proxies = proxies
|
|
67
|
+
@property
|
|
68
|
+
def proxy_string(self) -> str:
|
|
69
|
+
return self._proxy_config.proxy_string
|
|
70
|
+
@proxy_string.setter
|
|
71
|
+
def proxy_string(self, proxies:str) -> None:
|
|
72
|
+
self._proxy_config.proxy_string = proxies
|
|
73
|
+
|
|
74
|
+
@property
|
|
75
|
+
def policy_file(self) -> str:
|
|
76
|
+
return self._policy_file
|
|
77
|
+
def set_policy_file(self, policy_file:str, *, ckan:CkanApi=None, base_dir:str=None, proxies:dict=None,
|
|
78
|
+
error_not_found:bool=True) -> None:
|
|
79
|
+
if proxies is None:
|
|
80
|
+
proxies = self._proxy_config.proxies
|
|
81
|
+
self._policy_file = policy_file
|
|
82
|
+
if policy_file is not None:
|
|
83
|
+
self._policy = None
|
|
84
|
+
if ckan is None:
|
|
85
|
+
ckan = self.init_ckan(base_dir=base_dir) # initiate a temporary ckan object to enable the load of the default policy
|
|
86
|
+
# self._policy = CkanPackageDataFormatPolicy.from_json(policy_file, base_dir=base_dir, proxies=proxies, error_not_found=error_not_found)
|
|
87
|
+
self._policy = ckan.load_policy(policy_file, base_dir=base_dir, proxies=proxies, error_not_found=error_not_found)
|
|
88
|
+
self._policy_file = self._policy.source_file
|
|
89
|
+
else:
|
|
90
|
+
self._policy = None
|
|
91
|
+
@property
|
|
92
|
+
def policy(self) -> CkanPackageDataFormatPolicy:
|
|
93
|
+
return self._policy
|
|
94
|
+
|
|
95
|
+
def _load_from_df(self, ckan_df: pd.DataFrame, base_dir: str,
|
|
96
|
+
proxies:dict, error_not_found:bool=True) -> None:
|
|
97
|
+
"""
|
|
98
|
+
Function to load builder parameters from a DataFrame, usually from an Excel worksheet
|
|
99
|
+
|
|
100
|
+
:param ckan_df:
|
|
101
|
+
:return:
|
|
102
|
+
"""
|
|
103
|
+
ckan_df.columns = ckan_df.columns.map(str.lower)
|
|
104
|
+
ckan_df.columns = ckan_df.columns.map(str.strip)
|
|
105
|
+
# order is important here:
|
|
106
|
+
if "ckan url" in ckan_df.columns:
|
|
107
|
+
self.url = _string_from_element(ckan_df.pop("ckan url"))
|
|
108
|
+
if "ckan api key file" in ckan_df.columns:
|
|
109
|
+
self.apikey_file = _string_from_element(ckan_df.pop("ckan api key file"))
|
|
110
|
+
if "proxies" in ckan_df.columns:
|
|
111
|
+
self._proxy_config.proxy_string = _string_from_element(ckan_df.pop("proxies"))
|
|
112
|
+
if "proxy authentication file" in ckan_df.columns:
|
|
113
|
+
proxy_auth_file = _string_from_element(ckan_df.pop("proxy authentication file"))
|
|
114
|
+
if proxy_auth_file is not None:
|
|
115
|
+
self._proxy_config.load_proxy_auth_from_file(proxy_auth_file, base_dir=base_dir)
|
|
116
|
+
self.ckan_ca = None
|
|
117
|
+
self._ckan_ca_src = None
|
|
118
|
+
if "ckan remote ca" in ckan_df.columns:
|
|
119
|
+
ca_cert = _string_from_element(ckan_df.pop("ckan remote ca"))
|
|
120
|
+
self.ckan_ca, self._ckan_ca_src = ca_file_rel_to_dir(ca_cert, base_dir)
|
|
121
|
+
self.extern_ca = None
|
|
122
|
+
self._extern_ca_src = None
|
|
123
|
+
if "external ca" in ckan_df.columns:
|
|
124
|
+
ca_cert = _string_from_element(ckan_df.pop("external ca"))
|
|
125
|
+
self.extern_ca, self._extern_ca_src = ca_file_rel_to_dir(ca_cert, base_dir)
|
|
126
|
+
if "data format policy file" in ckan_df.columns:
|
|
127
|
+
policy_file = _string_from_element(ckan_df.pop("data format policy file"))
|
|
128
|
+
self.set_policy_file(policy_file, base_dir=base_dir, proxies=proxies,
|
|
129
|
+
error_not_found=error_not_found)
|
|
130
|
+
if "options" in ckan_df.columns:
|
|
131
|
+
self.options_string = _string_from_element(ckan_df.pop("options"))
|
|
132
|
+
if "comment" in ckan_df.columns:
|
|
133
|
+
self.comment = _string_from_element(ckan_df.pop("comment"))
|
|
134
|
+
|
|
135
|
+
def _to_dict(self, base_dir:str) -> dict:
|
|
136
|
+
"""
|
|
137
|
+
Function to export builder parameters to an Excel worksheet, using the same fields as the input format
|
|
138
|
+
|
|
139
|
+
:see: _load_from_df
|
|
140
|
+
:see: to_xls
|
|
141
|
+
:return:
|
|
142
|
+
"""
|
|
143
|
+
ckan_dict = dict()
|
|
144
|
+
ckan_dict["CKAN URL"] = self.url
|
|
145
|
+
ckan_dict["CKAN API key file"] = self.apikey_file
|
|
146
|
+
ckan_dict["Proxies"] = self._proxy_config.proxy_string
|
|
147
|
+
ckan_dict["Proxy authentication file"] = make_path_relative(self._proxy_config.proxy_auth_file, base_dir)
|
|
148
|
+
ckan_dict["CKAN remote CA"] = ca_arg_to_str(self.ckan_ca, base_dir=base_dir, source_string=self._ckan_ca_src)
|
|
149
|
+
ckan_dict["External remote CA"] = ca_arg_to_str(self.extern_ca, base_dir=base_dir, source_string=self._extern_ca_src)
|
|
150
|
+
ckan_dict["Data format policy file"] = make_path_relative(self.policy_file, base_dir)
|
|
151
|
+
ckan_dict["Options"] = self.options_string
|
|
152
|
+
ckan_dict["Comment"] = self.comment
|
|
153
|
+
return ckan_dict
|
|
154
|
+
|
|
155
|
+
def _get_builder_df_help_dict(self) -> dict:
|
|
156
|
+
ckan_help_dict = {
|
|
157
|
+
"CKAN URL": "URL of the CKAN server e.g. https://demo.ckan.org/",
|
|
158
|
+
"CKAN API key file": "Path to a file containing the API key in the first line",
|
|
159
|
+
"Proxies": 'Proxies configuration, either one unique url or {"http": "http://proxy:8082", "https": "http://proxy:8082"}',
|
|
160
|
+
"Proxy authentication file": "Path to a text file containing 3 lines with the proxy authentication method, username and password, relative to this Excel workbook folder. "
|
|
161
|
+
+ "This applies to all connexions (to CKAN server and external resources)",
|
|
162
|
+
"CKAN remote CA": "Path to a custom CA certificate for the CKAN server (.pem), relative to this Excel workbook folder",
|
|
163
|
+
"External CA": "Path to a custom CA certificate used for connexions other than the CKAN server, relative to this Excel workbook folder (.pem)",
|
|
164
|
+
"Data format policy file": "Path to a JSON file containing the CKAN data format policy, relative to this Excel workbook folder",
|
|
165
|
+
"Options": "List of options to initialize the CKAN API object in CLI format",
|
|
166
|
+
}
|
|
167
|
+
return ckan_help_dict
|
|
168
|
+
|
|
169
|
+
def _load_from_dict(self, ckan_dict: dict, base_dir: str, proxies:dict=None) -> None:
|
|
170
|
+
ckan_df = pd.DataFrame([ckan_dict], index=["Value"])
|
|
171
|
+
ckan_df = ckan_df.transpose()
|
|
172
|
+
ckan_df.index.name = "Attribute"
|
|
173
|
+
ckan_df = ckan_df.transpose()
|
|
174
|
+
self._load_from_df(ckan_df, base_dir=base_dir, proxies=proxies)
|
|
175
|
+
|
|
176
|
+
def _get_builder_df(self, base_dir:str) -> pd.DataFrame:
|
|
177
|
+
"""
|
|
178
|
+
Converts the result of method _to_dict() into a DataFrame
|
|
179
|
+
|
|
180
|
+
:return:
|
|
181
|
+
"""
|
|
182
|
+
ckan_dict = self._to_dict(base_dir=base_dir)
|
|
183
|
+
ckan_help_dict = self._get_builder_df_help_dict()
|
|
184
|
+
ckan_df = pd.DataFrame([ckan_dict, ckan_help_dict], index=["Value", "Help"])
|
|
185
|
+
ckan_df = ckan_df.transpose()
|
|
186
|
+
ckan_df.index.name = "Attribute"
|
|
187
|
+
return ckan_df
|
|
188
|
+
|
|
189
|
+
def from_ckan(self, ckan: CkanApi) -> None:
|
|
190
|
+
"""
|
|
191
|
+
Initialize fields from a CKAN instance.
|
|
192
|
+
"""
|
|
193
|
+
self.url = ckan.url
|
|
194
|
+
self.apikey_file = ckan.apikey.apikey_file
|
|
195
|
+
self._proxy_config = ckan.params._proxy_config
|
|
196
|
+
self.ckan_ca = ckan.params.ckan_ca
|
|
197
|
+
self.extern_ca = ckan.params.extern_ca
|
|
198
|
+
if ckan.policy is not None and ckan.policy_source is not None:
|
|
199
|
+
self.set_policy_file(ckan.policy_source)
|
|
200
|
+
|
|
201
|
+
def init_ckan(self, base_dir:str, ckan:CkanApi=None, default_proxies:dict=None,
|
|
202
|
+
proxies:Union[str,dict,ProxyConfig]=None) -> CkanApi:
|
|
203
|
+
"""
|
|
204
|
+
Initialize a CKAN instance, following the parameters of the Excel workbook.
|
|
205
|
+
The parameters from Excel have precedence on the values already contained in the CKAN object.
|
|
206
|
+
However, the Excel workbook might not contain sufficient information.
|
|
207
|
+
|
|
208
|
+
:param base_dir:
|
|
209
|
+
:param ckan:
|
|
210
|
+
:param default_proxies:
|
|
211
|
+
:param proxies:
|
|
212
|
+
:return:
|
|
213
|
+
"""
|
|
214
|
+
if ckan is None:
|
|
215
|
+
ckan = CkanApi(url=self.url)
|
|
216
|
+
if self.url is not None:
|
|
217
|
+
ckan.url = self.url.strip()
|
|
218
|
+
if self.apikey_file is not None:
|
|
219
|
+
ckan.load_apikey(self.apikey_file, base_dir=base_dir)
|
|
220
|
+
if self.ckan_ca is not None:
|
|
221
|
+
ckan.ckan_ca = self.ckan_ca
|
|
222
|
+
if self.extern_ca is not None:
|
|
223
|
+
ckan.extern_ca = self.extern_ca
|
|
224
|
+
if proxies is not None:
|
|
225
|
+
# proxies given by argument are prioritary over those specified in the builder
|
|
226
|
+
ckan.set_proxies(proxies)
|
|
227
|
+
elif self._proxy_config.is_defined():
|
|
228
|
+
ckan._proxy_config = self._proxy_config
|
|
229
|
+
ckan._proxy_config.replace_default_proxy(default_proxies)
|
|
230
|
+
elif default_proxies is not None:
|
|
231
|
+
ckan._proxy_config.proxies = default_proxies
|
|
232
|
+
if self.policy is not None:
|
|
233
|
+
ckan.policy = self.policy
|
|
234
|
+
if self.options_string is not None:
|
|
235
|
+
ckan.initialize_from_options_string(self.options_string,
|
|
236
|
+
base_dir=base_dir, default_proxies=default_proxies)
|
|
237
|
+
return ckan
|
|
238
|
+
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
#!python3
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
"""
|
|
4
|
+
Data model to represent a CKAN database architecture
|
|
5
|
+
"""
|
|
6
|
+
from typing import Iterable
|
|
7
|
+
from ckanapi_harvesters.auxiliary.error_level_message import ContextErrorLevelMessage, ErrorLevel
|
|
8
|
+
|
|
9
|
+
## Exceptions ------------------
|
|
10
|
+
class EmptyPackageNameException(RuntimeError):
|
|
11
|
+
def __init__(self):
|
|
12
|
+
super().__init__("Run-time error: the attribute package_name cannot be empty")
|
|
13
|
+
|
|
14
|
+
class MissingDataStoreInfoError(Exception):
|
|
15
|
+
def __init__(self):
|
|
16
|
+
super().__init__("DataStore info must be requested to initiate resource builder. Use option datastore_info=True for the map_resources function.")
|
|
17
|
+
|
|
18
|
+
class RequiredDataFrameFieldsError(Exception):
|
|
19
|
+
def __init__(self, missing_fields:Iterable[str]):
|
|
20
|
+
super().__init__("The following fields are required but absent from the sample DataFrame: {}".format(", ".join(missing_fields)))
|
|
21
|
+
|
|
22
|
+
class UnsupportedBuilderVersionError(Exception):
|
|
23
|
+
def __init__(self, file_version):
|
|
24
|
+
super().__init__(f"Version error: package builder version {file_version} is not supported")
|
|
25
|
+
|
|
26
|
+
class MissingCodeFileError(Exception):
|
|
27
|
+
def __init__(self):
|
|
28
|
+
super().__init__("Function names were provided but Auxiliary functions file was not specified")
|
|
29
|
+
|
|
30
|
+
class ResourceFileNotExistMessage(ContextErrorLevelMessage):
|
|
31
|
+
def __init__(self, resource_name:str, error_level:ErrorLevel, specific_message: str):
|
|
32
|
+
super().__init__(f"Resource {resource_name}", error_level, specific_message)
|
|
33
|
+
|
|
34
|
+
class IncompletePatchError(Exception):
|
|
35
|
+
pass
|
|
36
|
+
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
#!python3
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
"""
|
|
4
|
+
Code to upload metadata to the CKAN server to create/update an existing package
|
|
5
|
+
The metadata is defined by the user in an Excel worksheet
|
|
6
|
+
This file implements the field definition
|
|
7
|
+
"""
|
|
8
|
+
from typing import Union
|
|
9
|
+
import pandas as pd
|
|
10
|
+
|
|
11
|
+
from ckanapi_harvesters.auxiliary.ckan_model import CkanFieldType, CkanField
|
|
12
|
+
from ckanapi_harvesters.auxiliary.ckan_auxiliary import CkanFieldInternalAttrs
|
|
13
|
+
from ckanapi_harvesters.auxiliary.ckan_auxiliary import _string_from_element, _bool_from_string
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class BuilderField:
|
|
19
|
+
def __init__(self, *, name:str=None, type_override:CkanFieldType=None,
|
|
20
|
+
label:str=None, description:str=None):
|
|
21
|
+
self.name: str = name
|
|
22
|
+
self.type_override: Union[CkanFieldType,None] = type_override
|
|
23
|
+
self.label: Union[str,None] = label
|
|
24
|
+
self.description: Union[str,None] = description
|
|
25
|
+
self.is_index: Union[bool,None] = None
|
|
26
|
+
self.uniquekey: Union[bool,None] = None
|
|
27
|
+
self.notnull: Union[bool,None] = None
|
|
28
|
+
self.options_string: Union[str,None] = None
|
|
29
|
+
self.internal_attrs: CkanFieldInternalAttrs = CkanFieldInternalAttrs()
|
|
30
|
+
self.comment: Union[str,None] = None
|
|
31
|
+
|
|
32
|
+
def __str__(self):
|
|
33
|
+
return f"Field builder for {self.name}"
|
|
34
|
+
|
|
35
|
+
def copy(self, *, dest=None):
|
|
36
|
+
if dest is None:
|
|
37
|
+
dest = BuilderField()
|
|
38
|
+
dest.name = self.name
|
|
39
|
+
dest.type_override = self.type_override
|
|
40
|
+
dest.label = self.label
|
|
41
|
+
dest.description = self.description
|
|
42
|
+
dest.is_index = self.is_index
|
|
43
|
+
dest.uniquekey = self.uniquekey
|
|
44
|
+
dest.notnull = self.notnull
|
|
45
|
+
dest.options_string = self.options_string
|
|
46
|
+
dest.internal_attrs = self.internal_attrs.copy()
|
|
47
|
+
dest.comment = self.comment
|
|
48
|
+
return dest
|
|
49
|
+
|
|
50
|
+
def _load_from_df_row(self, row: pd.Series):
|
|
51
|
+
self.name = _string_from_element(row["field name"]).strip()
|
|
52
|
+
type_override_string = _string_from_element(row["type override"])
|
|
53
|
+
self.type_override = None
|
|
54
|
+
if type_override_string is not None:
|
|
55
|
+
self.type_override = CkanFieldType.from_str(type_override_string)
|
|
56
|
+
else:
|
|
57
|
+
self.type_override = None
|
|
58
|
+
self.label = None
|
|
59
|
+
self.description = None
|
|
60
|
+
if "label" in row.keys():
|
|
61
|
+
self.label = _string_from_element(row["label"])
|
|
62
|
+
if "description" in row.keys():
|
|
63
|
+
self.description = _string_from_element(row["description"])
|
|
64
|
+
if "index" in row.keys():
|
|
65
|
+
self.is_index = _bool_from_string(row["index"], default_value=None)
|
|
66
|
+
if "unique" in row.keys():
|
|
67
|
+
self.uniquekey = _bool_from_string(row["unique"], default_value=None)
|
|
68
|
+
if "not null" in row.keys():
|
|
69
|
+
self.notnull = _bool_from_string(row["not null"], default_value=None)
|
|
70
|
+
if "options" in row.keys():
|
|
71
|
+
self.options_string = _string_from_element(row["options"])
|
|
72
|
+
if "comment" in row.keys():
|
|
73
|
+
self.comment = _string_from_element(row["comment"])
|
|
74
|
+
self.internal_attrs.init_from_native_type(self.type_override)
|
|
75
|
+
self.internal_attrs.init_from_options_string(self.options_string)
|
|
76
|
+
|
|
77
|
+
@staticmethod
|
|
78
|
+
def from_df_row(row: pd.Series) -> "BuilderField":
|
|
79
|
+
field_builder = BuilderField()
|
|
80
|
+
field_builder._load_from_df_row(row)
|
|
81
|
+
return field_builder
|
|
82
|
+
|
|
83
|
+
def _to_dict(self) -> dict:
|
|
84
|
+
return {
|
|
85
|
+
"Field Name": self.name,
|
|
86
|
+
"Type override": str(self.type_override) if self.type_override is not None else "",
|
|
87
|
+
"Label": self.label if self.label else "",
|
|
88
|
+
"Description": self.description if self.description else "",
|
|
89
|
+
"Index": str(self.is_index) if self.is_index is not None else "",
|
|
90
|
+
"Unique": str(self.uniquekey) if self.uniquekey is not None else "",
|
|
91
|
+
"Not null": str(self.notnull) if self.notnull is not None else "",
|
|
92
|
+
"Options": self.options_string if self.options_string else "",
|
|
93
|
+
"Comment": self.comment if self.comment else "",
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
def _to_ckan_field(self) -> CkanField:
|
|
97
|
+
field_info = CkanField(name=self.name, data_type=str(self.type_override) if self.type_override is not None else "",
|
|
98
|
+
notes=self.description, label=self.label)
|
|
99
|
+
field_info.is_index = self.is_index
|
|
100
|
+
field_info.uniquekey = self.uniquekey
|
|
101
|
+
field_info.notnull = self.notnull
|
|
102
|
+
field_info.internal_attrs = self.internal_attrs.copy()
|
|
103
|
+
return field_info
|
|
104
|
+
|
|
105
|
+
def _to_ckan_dict(self) -> dict:
|
|
106
|
+
return self._to_ckan_field().to_ckan_dict()
|
|
107
|
+
|
|
108
|
+
@staticmethod
|
|
109
|
+
def _from_ckan_field(field_info: CkanField) -> "BuilderField":
|
|
110
|
+
field_row = pd.Series({"field name": field_info.name,
|
|
111
|
+
"type override": str(field_info.data_type), # if field_info.type_override else "",
|
|
112
|
+
"label": field_info.label,
|
|
113
|
+
"description": field_info.notes,
|
|
114
|
+
"index": field_info.is_index,
|
|
115
|
+
"unique": field_info.uniquekey,
|
|
116
|
+
"not null": field_info.notnull,
|
|
117
|
+
})
|
|
118
|
+
field_builder = BuilderField()
|
|
119
|
+
field_builder._load_from_df_row(field_row)
|
|
120
|
+
field_builder.internal_attrs = field_info.internal_attrs.copy()
|
|
121
|
+
return field_builder
|
|
122
|
+
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
#!python3
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
"""
|
|
4
|
+
Alias to most complete BuilderPackage implementation
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from ckanapi_harvesters.builder.builder_package_1_basic import example_package_xls
|
|
8
|
+
from ckanapi_harvesters.builder.builder_package_2_harvesters import BuilderPackageWithHarvesters as BuilderPackage # alias
|
|
9
|
+
|