Example: Building a dataset from an Excel file
The Python package ckanapi_harvesters.builder implements functions to manage a CKAN dataset (previously known as a package) with the help of an Excel workbook. This Excel file specifies the dataset metadata and information to upload/download the resources of the dataset. An illustration of these tasks is given in this notebook.
Initialisation
The Python package and its extra dependencies can be installed with the following command:
> pip install ckanapi-harvesters[extras]
The following cell refers to the code present in the Git directory with the option use_git_package.
# initial checks
import sys
print(f"Python version: {sys.version} in {sys.executable}")
# optionally, use the ckanapi_harvesters package present in the Git directory
use_git_package = False
if use_git_package:
import os
cwd = os.getcwd()
if not os.path.isdir(os.path.join(cwd, "ckanapi_harvesters")):
# we assume we are in the examples directory
cwd = os.path.join(cwd, r"../../src") # aim for src directory
assert(os.path.isdir(os.path.join(cwd, "ckanapi_harvesters")))
os.chdir(cwd)
print("CWD changed to: " + os.path.abspath(""))
import os
from ckanapi_harvesters import CkanApi, BuilderPackage, CkanCallbackLevel
from ckanapi_harvesters.builder.example import example_package_xls
from ckanapi_harvesters import __version__ as ckanapi_harvesters_version
from ckanapi_harvesters import package_dir as ckanapi_package_dir
print(f"ckanapi-harvesters version: {ckanapi_harvesters_version} in {ckanapi_package_dir}")
Script configuration
CKAN URL
Owner organisation
Excel file
API key file
Proxies
ckan_url = "https://demo.ckan.org/"
ckan_url = None # use this line if the CKAN URL is specified in the Excel workbook / user input
# Owner organization for the new dataset
owner_org = "test"
owner_org = None # use this line if the owner organisation is specified in the Excel workbook / user input
xls_file = example_package_xls # Excel file
print("Excel file: " + xls_file)
if not os.path.exists(xls_file):
print("Excel file not found !!!")
apikey_file = os.path.expanduser(os.path.join("~", ".config", "__CKAN_API_KEY__.txt")) # default location: ~/.config/__CKAN_API_KEY__.txt
apikey_file = None # if not specified, the package will look in the different locations and environment variables
print("API key file: " + str(apikey_file))
if apikey_file is not None and not os.path.exists(apikey_file):
print("API key file not found !!!")
# proxy configuration
proxies = {"http": "http://myproxy", "https": "http://myproxy"} # example
proxies = {"http": "", "https": ""} # no proxies
proxies = None # use the system configuration, defined in your environment variables
print("proxies = " + str(proxies))
Loading dataset metadata from Excel file
The Excel workbook given in the example refers to an external code module for DataFrame upload/download functions. To activate this feature, a call to unlock_external_code_execution must be done.
Warning: Only enable this feature for code which comes from trusted sources as this executes the module referred in the Excel workbook (Auxiliary functions file field).
BuilderPackage.unlock_external_code_execution()
mdl = BuilderPackage.from_excel(xls_file)
print(f"Excel file loaded for dataset '{mdl.package_attributes.title}'")
print(f"The URL will be: {mdl.get_package_page_url(ckan)}")
Connecting to CKAN
ckan = CkanApi(ckan_url, proxies=proxies, apikey_file=apikey_file, owner_org=owner_org)
# you can specify the CKAN URL, owner organization, API key here or in the Excel workbook
ckan = mdl.init_ckan(ckan)
ckan.input_missing_info(input_args_if_necessary=True, input_owner_org=True, error_not_found=False) # request user input to configure CKAN
ckan.set_limits_per_request(10000) # reduce if server hangs up
ckan.set_requests_delay(0.1) # increase if server errors 502
ckan.set_verbosity(True) # this displays all the steps performed by the script
ckan.test_ckan_login(raise_error=True, verbose=True) # test if you are correctly logged in
Displaying the dataset model
df_dict = mdl.get_all_df()
for tab, df in df_dict.items():
display(f"Tab {tab}:")
display(df)
Initiating the dataset
This call creates the dataset if no other dataset with the same name exists. If the dataset already exists, it is updated with information from the Excel workbook. The resources are initialized. Optionally, the resources data can be fully reuploaded (even if the resources already exist) to ensure the server side of the dataset represents the information specified in the Excel workbook. However, if there are large datasets, this resets them.
reupload = True # True: reuploads all documents and resets large datasets to the first document (not recommended if there is a large dataset)
mdl.patch_request_full(ckan, reupload=reupload)
Uploading large datasets
Large datasets are defined locally by a directory containing multiple CSV files. The first file found (in alphabetic order) is used to initialize the dataset. This function automates the concatenation of other files using the API datastore_upsert in a multi-threaded implementation. It can be executed multiple times without affecting the final result, as long as all the data has been transferred.
The number of threads should be adjusted if there are too many request errors.
threads = 3 # > 1: multi-threading mode - reduce if HTTP 502 errors
mdl.upload_large_datasets(ckan, threads=threads, only_missing=True)
Final requests
mdl.patch_request_final(ckan) # sets dataset state, reorders resources, updates dataset policy fields - also called at the end of upload_large_datasets
Updating metadata policy scores
mdl.remote_policy_check(ckan, verbose=True)
Database queries
Querying the dataset as a database table. Check the format of the first few lines with this method.
users_id = mdl.get_or_query_resource_id(ckan, "users.csv")
traces_id = mdl.get_or_query_resource_id(ckan, "traces.csv")
Simple requests
Using API datastore_search.
cursor = ckan.datastore_search_cursor(users_id, total_limit=1)
document = next(cursor)
user_id = document["user_id"]
cursor = ckan.datastore_search_cursor(traces_id, filters={"user_id": int(user_id)}, total_limit=10)
for document in cursor:
print(document)
SQL queries
Example of an SQL query joining two tables using API datastore_search_sql.
query = f"""
SELECT t.*, u.* FROM "{traces_id}" t
JOIN "{users_id}" u ON t.user_id = u.user_id
WHERE t.user_id = {user_id}
LIMIT 10
"""
cursor = ckan.datastore_search_sql_cursor(query)
for document in cursor:
print(document)
Restoring original files
This function downloads all the resources of a dataset to CSV files. The multi-threaded implementation is reserved to download large datasets.
# define the destination directory
example_package_download_dir = os.path.abspath("package_download")
print("Dataset will be downloaded in: " + example_package_download_dir)
threads = 3 # > 1: number of threads to download large datasets
mdl.download_request_full(ckan, example_package_download_dir, full_download=True, threads=threads, skip_existing=False)