hdx-python-api 6.6.4__py3-none-any.whl → 6.6.6__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.
- hdx/api/_version.py +8 -18
- hdx/data/dataset.py +29 -400
- hdx/data/resource.py +0 -22
- {hdx_python_api-6.6.4.dist-info → hdx_python_api-6.6.6.dist-info}/METADATA +87 -4
- {hdx_python_api-6.6.4.dist-info → hdx_python_api-6.6.6.dist-info}/RECORD +7 -10
- {hdx_python_api-6.6.4.dist-info → hdx_python_api-6.6.6.dist-info}/WHEEL +1 -1
- hdx/api/utilities/dataset_title_helper.py +0 -254
- hdx/data/hdx_datasource_topline.yaml +0 -30
- hdx/data/indicator_resource_view_template.yaml +0 -4
- {hdx_python_api-6.6.4.dist-info → hdx_python_api-6.6.6.dist-info}/licenses/LICENSE +0 -0
hdx/api/_version.py
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
# file generated by
|
|
1
|
+
# file generated by vcs-versioning
|
|
2
2
|
# don't change, don't track in version control
|
|
3
|
+
from __future__ import annotations
|
|
3
4
|
|
|
4
5
|
__all__ = [
|
|
5
6
|
"__version__",
|
|
@@ -10,25 +11,14 @@ __all__ = [
|
|
|
10
11
|
"commit_id",
|
|
11
12
|
]
|
|
12
13
|
|
|
13
|
-
TYPE_CHECKING = False
|
|
14
|
-
if TYPE_CHECKING:
|
|
15
|
-
from typing import Tuple
|
|
16
|
-
from typing import Union
|
|
17
|
-
|
|
18
|
-
VERSION_TUPLE = Tuple[Union[int, str], ...]
|
|
19
|
-
COMMIT_ID = Union[str, None]
|
|
20
|
-
else:
|
|
21
|
-
VERSION_TUPLE = object
|
|
22
|
-
COMMIT_ID = object
|
|
23
|
-
|
|
24
14
|
version: str
|
|
25
15
|
__version__: str
|
|
26
|
-
__version_tuple__:
|
|
27
|
-
version_tuple:
|
|
28
|
-
commit_id:
|
|
29
|
-
__commit_id__:
|
|
16
|
+
__version_tuple__: tuple[int | str, ...]
|
|
17
|
+
version_tuple: tuple[int | str, ...]
|
|
18
|
+
commit_id: str | None
|
|
19
|
+
__commit_id__: str | None
|
|
30
20
|
|
|
31
|
-
__version__ = version = '6.6.
|
|
32
|
-
__version_tuple__ = version_tuple = (6, 6,
|
|
21
|
+
__version__ = version = '6.6.6'
|
|
22
|
+
__version_tuple__ = version_tuple = (6, 6, 6)
|
|
33
23
|
|
|
34
24
|
__commit_id__ = commit_id = None
|
hdx/data/dataset.py
CHANGED
|
@@ -3,8 +3,7 @@
|
|
|
3
3
|
import json
|
|
4
4
|
import logging
|
|
5
5
|
import sys
|
|
6
|
-
import
|
|
7
|
-
from collections.abc import Callable, Iterable, Iterator, Mapping, Sequence
|
|
6
|
+
from collections.abc import Callable, Iterable, Mapping, Sequence
|
|
8
7
|
from copy import deepcopy
|
|
9
8
|
from datetime import datetime
|
|
10
9
|
from pathlib import Path
|
|
@@ -26,7 +25,6 @@ from hdx.utilities.dateparse import (
|
|
|
26
25
|
parse_date_range,
|
|
27
26
|
)
|
|
28
27
|
from hdx.utilities.dictandlist import merge_two_dictionaries
|
|
29
|
-
from hdx.utilities.downloader import Download
|
|
30
28
|
from hdx.utilities.loader import load_json
|
|
31
29
|
from hdx.utilities.saver import save_iterable, save_json
|
|
32
30
|
from hdx.utilities.uuid import is_valid_uuid
|
|
@@ -40,7 +38,6 @@ import hdx.data.user as user
|
|
|
40
38
|
import hdx.data.vocabulary as vocabulary
|
|
41
39
|
from hdx.api.configuration import Configuration
|
|
42
40
|
from hdx.api.locations import Locations
|
|
43
|
-
from hdx.api.utilities.dataset_title_helper import DatasetTitleHelper
|
|
44
41
|
from hdx.api.utilities.date_helper import DateHelper
|
|
45
42
|
from hdx.api.utilities.filestore_helper import FilestoreHelper
|
|
46
43
|
from hdx.data.hdxobject import HDXError, HDXObject
|
|
@@ -203,7 +200,7 @@ class Dataset(HDXObject):
|
|
|
203
200
|
session: Session | None = None,
|
|
204
201
|
) -> None:
|
|
205
202
|
"""Save dataset to JSON. If follow_urls is True, resource urls that point to
|
|
206
|
-
datasets
|
|
203
|
+
datasets are followed to retrieve final urls.
|
|
207
204
|
|
|
208
205
|
Args:
|
|
209
206
|
path: Path to save dataset
|
|
@@ -397,35 +394,6 @@ class Dataset(HDXObject):
|
|
|
397
394
|
"""
|
|
398
395
|
return len(self._resources)
|
|
399
396
|
|
|
400
|
-
def reorder_resources(
|
|
401
|
-
self, resource_ids: Sequence[str], hxl_update: bool = True
|
|
402
|
-
) -> None:
|
|
403
|
-
"""Reorder resources in dataset according to provided list. Resources are
|
|
404
|
-
updated in the dataset object to match new order. However, the dataset is not
|
|
405
|
-
refreshed by rereading from HDX. If only some resource ids are supplied then
|
|
406
|
-
these are assumed to be first and the other resources will stay in their
|
|
407
|
-
original order.
|
|
408
|
-
|
|
409
|
-
Args:
|
|
410
|
-
resource_ids: List of resource ids
|
|
411
|
-
|
|
412
|
-
Returns:
|
|
413
|
-
None
|
|
414
|
-
"""
|
|
415
|
-
dataset_id = self.data.get("id")
|
|
416
|
-
if not dataset_id:
|
|
417
|
-
raise HDXError(
|
|
418
|
-
"Dataset has no id! It must be read, created or updated first."
|
|
419
|
-
)
|
|
420
|
-
data = {"id": dataset_id, "order": resource_ids}
|
|
421
|
-
results = self._write_to_hdx("reorder", data)
|
|
422
|
-
ordered_ids = results["order"]
|
|
423
|
-
reordered_resources = []
|
|
424
|
-
for resource_id in ordered_ids:
|
|
425
|
-
resource = next(x for x in self._resources if x["id"] == resource_id)
|
|
426
|
-
reordered_resources.append(resource)
|
|
427
|
-
self._resources = reordered_resources
|
|
428
|
-
|
|
429
397
|
def move_resource(
|
|
430
398
|
self,
|
|
431
399
|
resource_name: str,
|
|
@@ -519,6 +487,33 @@ class Dataset(HDXObject):
|
|
|
519
487
|
self.init_resources()
|
|
520
488
|
self.separate_resources()
|
|
521
489
|
|
|
490
|
+
def reorder_resources(self, resource_ids: Sequence[str]) -> None:
|
|
491
|
+
"""Reorder resources in dataset according to provided list. Resources are
|
|
492
|
+
updated in the dataset object to match new order. However, the dataset is not
|
|
493
|
+
refreshed by rereading from HDX. If only some resource ids are supplied then
|
|
494
|
+
these are assumed to be first and the other resources will stay in their
|
|
495
|
+
original order.
|
|
496
|
+
|
|
497
|
+
Args:
|
|
498
|
+
resource_ids: List of resource ids
|
|
499
|
+
|
|
500
|
+
Returns:
|
|
501
|
+
None
|
|
502
|
+
"""
|
|
503
|
+
dataset_id = self.data.get("id")
|
|
504
|
+
if not dataset_id:
|
|
505
|
+
raise HDXError(
|
|
506
|
+
"Dataset has no id! It must be read, created or updated first."
|
|
507
|
+
)
|
|
508
|
+
data = {"id": dataset_id, "order": resource_ids}
|
|
509
|
+
results = self._write_to_hdx("reorder", data)
|
|
510
|
+
ordered_ids = results["order"]
|
|
511
|
+
reordered_resources = []
|
|
512
|
+
for resource_id in ordered_ids:
|
|
513
|
+
resource = next(x for x in self._resources if x["id"] == resource_id)
|
|
514
|
+
reordered_resources.append(resource)
|
|
515
|
+
self._resources = reordered_resources
|
|
516
|
+
|
|
522
517
|
def _dataset_load_from_hdx(self, id_or_name: str) -> bool:
|
|
523
518
|
"""Loads the dataset given by either id or name from HDX
|
|
524
519
|
|
|
@@ -1080,7 +1075,6 @@ class Dataset(HDXObject):
|
|
|
1080
1075
|
remove_additional_resources: bool = False,
|
|
1081
1076
|
match_resource_order: bool = False,
|
|
1082
1077
|
create_default_views: bool = True,
|
|
1083
|
-
hxl_update: bool = True,
|
|
1084
1078
|
**kwargs: Any,
|
|
1085
1079
|
) -> dict:
|
|
1086
1080
|
"""Check if dataset exists in HDX and if so, update it. match_resources_by_metadata uses ids if they are
|
|
@@ -1146,7 +1140,6 @@ class Dataset(HDXObject):
|
|
|
1146
1140
|
remove_additional_resources: bool = False,
|
|
1147
1141
|
match_resource_order: bool = False,
|
|
1148
1142
|
create_default_views: bool = True,
|
|
1149
|
-
hxl_update: bool = True,
|
|
1150
1143
|
**kwargs: Any,
|
|
1151
1144
|
) -> dict:
|
|
1152
1145
|
"""Check if dataset exists in HDX and if so, update it, otherwise create it. match_resources_by_metadata uses
|
|
@@ -1475,17 +1468,6 @@ class Dataset(HDXObject):
|
|
|
1475
1468
|
self.data.get("dataset_date"), date_format, today
|
|
1476
1469
|
)
|
|
1477
1470
|
|
|
1478
|
-
def get_reference_period(
|
|
1479
|
-
self,
|
|
1480
|
-
date_format: str | None = None,
|
|
1481
|
-
today: datetime = now_utc(),
|
|
1482
|
-
) -> dict:
|
|
1483
|
-
warnings.warn(
|
|
1484
|
-
"get_reference_period() is deprecated, use get_time_period() instead",
|
|
1485
|
-
DeprecationWarning,
|
|
1486
|
-
)
|
|
1487
|
-
return self.get_time_period(date_format, today)
|
|
1488
|
-
|
|
1489
1471
|
def set_time_period(
|
|
1490
1472
|
self,
|
|
1491
1473
|
startdate: datetime | str,
|
|
@@ -1512,19 +1494,6 @@ class Dataset(HDXObject):
|
|
|
1512
1494
|
startdate, enddate, ongoing, ignore_timeinfo
|
|
1513
1495
|
)
|
|
1514
1496
|
|
|
1515
|
-
def set_reference_period(
|
|
1516
|
-
self,
|
|
1517
|
-
startdate: datetime | str,
|
|
1518
|
-
enddate: datetime | str | None = None,
|
|
1519
|
-
ongoing: bool = False,
|
|
1520
|
-
ignore_timeinfo: bool = True,
|
|
1521
|
-
) -> None:
|
|
1522
|
-
warnings.warn(
|
|
1523
|
-
"set_reference_period() is deprecated, use set_time_period() instead",
|
|
1524
|
-
DeprecationWarning,
|
|
1525
|
-
)
|
|
1526
|
-
self.set_time_period(startdate, enddate, ongoing, ignore_timeinfo)
|
|
1527
|
-
|
|
1528
1497
|
def set_time_period_year_range(
|
|
1529
1498
|
self,
|
|
1530
1499
|
dataset_year: str | int | Iterable,
|
|
@@ -1545,17 +1514,6 @@ class Dataset(HDXObject):
|
|
|
1545
1514
|
) = DateHelper.get_hdx_time_period_from_years(dataset_year, dataset_end_year)
|
|
1546
1515
|
return retval
|
|
1547
1516
|
|
|
1548
|
-
def set_reference_period_year_range(
|
|
1549
|
-
self,
|
|
1550
|
-
dataset_year: str | int | Iterable,
|
|
1551
|
-
dataset_end_year: str | int | None = None,
|
|
1552
|
-
) -> list[int]:
|
|
1553
|
-
warnings.warn(
|
|
1554
|
-
"set_reference_period_year_range() is deprecated, use set_time_period_year_range() instead",
|
|
1555
|
-
DeprecationWarning,
|
|
1556
|
-
)
|
|
1557
|
-
return self.set_time_period_year_range(dataset_year, dataset_end_year)
|
|
1558
|
-
|
|
1559
1517
|
@classmethod
|
|
1560
1518
|
def list_valid_update_frequencies(cls) -> list[str]:
|
|
1561
1519
|
"""List of valid update frequency values
|
|
@@ -2266,15 +2224,6 @@ class Dataset(HDXObject):
|
|
|
2266
2224
|
dataset_resource.disable_dataset_preview()
|
|
2267
2225
|
return preview_resource
|
|
2268
2226
|
|
|
2269
|
-
def set_quickchart_resource(
|
|
2270
|
-
self, resource: Union["Resource", dict, str, int]
|
|
2271
|
-
) -> "Resource":
|
|
2272
|
-
warnings.warn(
|
|
2273
|
-
"set_quickchart_resource() is deprecated, use set_preview_resource() instead",
|
|
2274
|
-
DeprecationWarning,
|
|
2275
|
-
)
|
|
2276
|
-
return self.set_preview_resource(resource)
|
|
2277
|
-
|
|
2278
2227
|
def create_default_views(self, create_datastore_views: bool = False) -> None:
|
|
2279
2228
|
"""Create default resource views for all resources in dataset
|
|
2280
2229
|
|
|
@@ -2305,28 +2254,6 @@ class Dataset(HDXObject):
|
|
|
2305
2254
|
self._preview_resourceview = None
|
|
2306
2255
|
break
|
|
2307
2256
|
|
|
2308
|
-
def generate_quickcharts(
|
|
2309
|
-
self,
|
|
2310
|
-
resource: Union["Resource", dict, str, int] = 0,
|
|
2311
|
-
path: Path | str | None = None,
|
|
2312
|
-
bites_disabled: Sequence[bool] | None = None,
|
|
2313
|
-
indicators: Sequence[dict] | None = None,
|
|
2314
|
-
findreplace: dict | None = None,
|
|
2315
|
-
) -> None:
|
|
2316
|
-
"""To be removed
|
|
2317
|
-
|
|
2318
|
-
Args:
|
|
2319
|
-
resource: Either resource id or name, resource metadata from a Resource object or a dictionary or position. Defaults to 0.
|
|
2320
|
-
path: Path to YAML resource view metadata. Defaults to None (config/hdx_resource_view_static.yaml or internal template).
|
|
2321
|
-
bites_disabled: Which QC bites should be disabled. Defaults to None (all bites enabled).
|
|
2322
|
-
indicators: Indicator codes, QC titles and units for resource view template. Defaults to None (don't use template).
|
|
2323
|
-
findreplace: Replacements for anything else in resource view. Defaults to None.
|
|
2324
|
-
|
|
2325
|
-
Returns:
|
|
2326
|
-
None
|
|
2327
|
-
"""
|
|
2328
|
-
return None
|
|
2329
|
-
|
|
2330
2257
|
def get_name_or_id(self, prefer_name: bool = True) -> str | None:
|
|
2331
2258
|
"""Get dataset name or id eg. for use in urls. If prefer_name is True,
|
|
2332
2259
|
name is preferred over id if available, otherwise id is preferred over
|
|
@@ -2379,33 +2306,6 @@ class Dataset(HDXObject):
|
|
|
2379
2306
|
return None
|
|
2380
2307
|
return f"{self.configuration.get_hdx_site_url()}/api/3/action/package_show?id={name_or_id}"
|
|
2381
2308
|
|
|
2382
|
-
def remove_dates_from_title(
|
|
2383
|
-
self, change_title: bool = True, set_time_period: bool = False
|
|
2384
|
-
) -> list[tuple[datetime, datetime]]:
|
|
2385
|
-
"""Remove dates from dataset title returning sorted the dates that were found in
|
|
2386
|
-
title. The title in the dataset metadata will be changed by default. The
|
|
2387
|
-
dataset's metadata field time period will not be changed by default, but if
|
|
2388
|
-
set_time_period is True, then the range with the lowest start date will be used
|
|
2389
|
-
to set the time period field.
|
|
2390
|
-
|
|
2391
|
-
Args:
|
|
2392
|
-
change_title: Whether to change the dataset title. Defaults to True.
|
|
2393
|
-
set_time_period: Whether to set time period from date or range in title. Defaults to False.
|
|
2394
|
-
|
|
2395
|
-
Returns:
|
|
2396
|
-
Date ranges found in title
|
|
2397
|
-
"""
|
|
2398
|
-
if "title" not in self.data:
|
|
2399
|
-
raise HDXError("Dataset has no title!")
|
|
2400
|
-
title = self.data["title"]
|
|
2401
|
-
newtitle, ranges = DatasetTitleHelper.get_dates_from_title(title)
|
|
2402
|
-
if change_title:
|
|
2403
|
-
self.data["title"] = newtitle
|
|
2404
|
-
if set_time_period and len(ranges) != 0:
|
|
2405
|
-
startdate, enddate = ranges[0]
|
|
2406
|
-
self.set_time_period(startdate, enddate)
|
|
2407
|
-
return ranges
|
|
2408
|
-
|
|
2409
2309
|
def generate_resource(
|
|
2410
2310
|
self,
|
|
2411
2311
|
folder: Path | str,
|
|
@@ -2541,193 +2441,6 @@ class Dataset(HDXObject):
|
|
|
2541
2441
|
retdict["rows"] = rows
|
|
2542
2442
|
return True, retdict
|
|
2543
2443
|
|
|
2544
|
-
def generate_resource_from_rows(
|
|
2545
|
-
self,
|
|
2546
|
-
folder: Path | str,
|
|
2547
|
-
filename: str,
|
|
2548
|
-
rows: Iterable[Sequence | Mapping],
|
|
2549
|
-
resourcedata: dict,
|
|
2550
|
-
headers: Sequence[str] | None = None,
|
|
2551
|
-
encoding: str | None = None,
|
|
2552
|
-
) -> Optional["Resource"]:
|
|
2553
|
-
"""Write rows to csv and create resource, adding it to the dataset.
|
|
2554
|
-
The headers argument is either a row number (rows start counting at
|
|
2555
|
-
1), or the actual headers defined as a list of strings. If not set, all
|
|
2556
|
-
rows will be treated as containing values.
|
|
2557
|
-
|
|
2558
|
-
Args:
|
|
2559
|
-
folder: Folder to which to write file containing rows
|
|
2560
|
-
filename: Filename of file to write rows
|
|
2561
|
-
rows: List of rows in dict or list form
|
|
2562
|
-
resourcedata: Resource data
|
|
2563
|
-
headers: List of headers. Defaults to None.
|
|
2564
|
-
encoding: Encoding to use. Defaults to None (infer encoding).
|
|
2565
|
-
|
|
2566
|
-
Returns:
|
|
2567
|
-
The created resource or None if not created
|
|
2568
|
-
"""
|
|
2569
|
-
warnings.warn(
|
|
2570
|
-
"generate_resource_from_rows() is deprecated, use generate_resource() instead",
|
|
2571
|
-
DeprecationWarning,
|
|
2572
|
-
)
|
|
2573
|
-
res, retdict = self.generate_resource(
|
|
2574
|
-
folder, filename, rows, resourcedata, headers, headers, "csv", encoding
|
|
2575
|
-
)
|
|
2576
|
-
return retdict["resource"] if res else None
|
|
2577
|
-
|
|
2578
|
-
def generate_resource_from_iterable(
|
|
2579
|
-
self,
|
|
2580
|
-
headers: Sequence[str],
|
|
2581
|
-
iterable: Iterable[Sequence | dict],
|
|
2582
|
-
hxltags: dict[str, str],
|
|
2583
|
-
folder: Path | str,
|
|
2584
|
-
filename: str,
|
|
2585
|
-
resourcedata: dict,
|
|
2586
|
-
datecol: int | str | None = None,
|
|
2587
|
-
yearcol: int | str | None = None,
|
|
2588
|
-
date_function: Callable[[dict], dict | None] | None = None,
|
|
2589
|
-
quickcharts: dict | None = None,
|
|
2590
|
-
encoding: str | None = None,
|
|
2591
|
-
) -> tuple[bool, dict]:
|
|
2592
|
-
"""Given headers and an iterable, write rows to csv and create
|
|
2593
|
-
resource, adding to it the dataset. The returned dictionary will
|
|
2594
|
-
contain the resource in the key resource, headers in the key headers
|
|
2595
|
-
and list of rows in the key rows.
|
|
2596
|
-
|
|
2597
|
-
The time period can optionally be set by supplying a column in
|
|
2598
|
-
which the date or year is to be looked up. Note that any timezone
|
|
2599
|
-
information is ignored and UTC assumed. Alternatively, a function can
|
|
2600
|
-
be supplied to handle any dates in a row. It should accept a row and
|
|
2601
|
-
should return None to ignore the row or a dictionary which can either
|
|
2602
|
-
be empty if there are no dates in the row or can be populated with
|
|
2603
|
-
keys startdate and/or enddate which are of type timezone-aware
|
|
2604
|
-
datetime. The lowest start date and highest end date are used to set
|
|
2605
|
-
the time period and are returned in the results dictionary in keys
|
|
2606
|
-
startdate and enddate.
|
|
2607
|
-
|
|
2608
|
-
Args:
|
|
2609
|
-
headers: Headers
|
|
2610
|
-
iterable: Iterable returning rows
|
|
2611
|
-
hxltags: Header to HXL hashtag mapping
|
|
2612
|
-
folder: Folder to which to write file containing rows
|
|
2613
|
-
filename: Filename of file to write rows
|
|
2614
|
-
resourcedata: Resource data
|
|
2615
|
-
datecol: Date column for setting time period. Defaults to None (don't set).
|
|
2616
|
-
yearcol: Year column for setting dataset year range. Defaults to None (don't set).
|
|
2617
|
-
date_function: Date function to call for each row. Defaults to None.
|
|
2618
|
-
encoding: Encoding to use. Defaults to None (infer encoding).
|
|
2619
|
-
|
|
2620
|
-
Returns:
|
|
2621
|
-
(True if resource added, dictionary of results)
|
|
2622
|
-
"""
|
|
2623
|
-
warnings.warn(
|
|
2624
|
-
"generate_resource_from_iterable() is deprecated, use generate_resource() instead",
|
|
2625
|
-
DeprecationWarning,
|
|
2626
|
-
)
|
|
2627
|
-
if [datecol, yearcol, date_function].count(None) < 2:
|
|
2628
|
-
raise HDXError("Supply one of datecol, yearcol or date_function!")
|
|
2629
|
-
retdict = {}
|
|
2630
|
-
if headers is None:
|
|
2631
|
-
return False, retdict
|
|
2632
|
-
rows = [Download.hxl_row(headers, hxltags, dict_form=True)]
|
|
2633
|
-
dates = [default_enddate, default_date]
|
|
2634
|
-
if yearcol is not None:
|
|
2635
|
-
|
|
2636
|
-
def yearcol_function(row):
|
|
2637
|
-
result = {}
|
|
2638
|
-
year = row[yearcol]
|
|
2639
|
-
if year:
|
|
2640
|
-
result["startdate"], result["enddate"] = parse_date_range(
|
|
2641
|
-
year,
|
|
2642
|
-
zero_time=True,
|
|
2643
|
-
max_endtime=True,
|
|
2644
|
-
)
|
|
2645
|
-
return result
|
|
2646
|
-
|
|
2647
|
-
date_function = yearcol_function
|
|
2648
|
-
elif datecol is not None:
|
|
2649
|
-
|
|
2650
|
-
def datecol_function(row):
|
|
2651
|
-
result = {}
|
|
2652
|
-
date = row[datecol]
|
|
2653
|
-
if date:
|
|
2654
|
-
date = parse_date(date)
|
|
2655
|
-
result["startdate"] = date
|
|
2656
|
-
result["enddate"] = date
|
|
2657
|
-
return result
|
|
2658
|
-
|
|
2659
|
-
date_function = datecol_function
|
|
2660
|
-
|
|
2661
|
-
for row in iterable:
|
|
2662
|
-
if date_function is not None:
|
|
2663
|
-
result = date_function(row)
|
|
2664
|
-
if result is None:
|
|
2665
|
-
continue
|
|
2666
|
-
startdate = result.get("startdate")
|
|
2667
|
-
if startdate is not None:
|
|
2668
|
-
if startdate < dates[0]:
|
|
2669
|
-
dates[0] = startdate
|
|
2670
|
-
enddate = result.get("enddate")
|
|
2671
|
-
if enddate is not None:
|
|
2672
|
-
if enddate > dates[1]:
|
|
2673
|
-
dates[1] = enddate
|
|
2674
|
-
rows.append(row)
|
|
2675
|
-
if len(rows) == 1:
|
|
2676
|
-
logger.error(f"No data rows in {filename}!")
|
|
2677
|
-
return False, retdict
|
|
2678
|
-
if yearcol is not None or date_function is not None:
|
|
2679
|
-
if dates[0] == default_enddate or dates[1] == default_date:
|
|
2680
|
-
logger.error(f"No dates in {filename}!")
|
|
2681
|
-
return False, retdict
|
|
2682
|
-
else:
|
|
2683
|
-
retdict["startdate"] = dates[0]
|
|
2684
|
-
retdict["enddate"] = dates[1]
|
|
2685
|
-
self.set_time_period(dates[0], dates[1])
|
|
2686
|
-
resource = self.generate_resource_from_rows(
|
|
2687
|
-
folder,
|
|
2688
|
-
filename,
|
|
2689
|
-
rows,
|
|
2690
|
-
resourcedata,
|
|
2691
|
-
headers=headers,
|
|
2692
|
-
encoding=encoding,
|
|
2693
|
-
)
|
|
2694
|
-
retdict["resource"] = resource
|
|
2695
|
-
retdict["headers"] = headers
|
|
2696
|
-
retdict["rows"] = rows
|
|
2697
|
-
return True, retdict
|
|
2698
|
-
|
|
2699
|
-
def generate_resource_from_iterator(
|
|
2700
|
-
self,
|
|
2701
|
-
headers: Sequence[str],
|
|
2702
|
-
iterator: Iterator[Sequence | dict],
|
|
2703
|
-
hxltags: dict[str, str],
|
|
2704
|
-
folder: Path | str,
|
|
2705
|
-
filename: str,
|
|
2706
|
-
resourcedata: dict,
|
|
2707
|
-
datecol: int | str | None = None,
|
|
2708
|
-
yearcol: int | str | None = None,
|
|
2709
|
-
date_function: Callable[[dict], dict | None] | None = None,
|
|
2710
|
-
quickcharts: dict | None = None,
|
|
2711
|
-
encoding: str | None = None,
|
|
2712
|
-
) -> tuple[bool, dict]:
|
|
2713
|
-
warnings.warn(
|
|
2714
|
-
"generate_resource_from_iterator() is deprecated, use generate_resource() instead",
|
|
2715
|
-
DeprecationWarning,
|
|
2716
|
-
)
|
|
2717
|
-
return self.generate_resource_from_iterable(
|
|
2718
|
-
headers,
|
|
2719
|
-
iterator,
|
|
2720
|
-
hxltags,
|
|
2721
|
-
folder,
|
|
2722
|
-
filename,
|
|
2723
|
-
resourcedata,
|
|
2724
|
-
datecol,
|
|
2725
|
-
yearcol,
|
|
2726
|
-
date_function,
|
|
2727
|
-
quickcharts,
|
|
2728
|
-
encoding,
|
|
2729
|
-
)
|
|
2730
|
-
|
|
2731
2444
|
def download_generate_resource(
|
|
2732
2445
|
self,
|
|
2733
2446
|
downloader: BaseDownload,
|
|
@@ -2773,7 +2486,6 @@ class Dataset(HDXObject):
|
|
|
2773
2486
|
Args:
|
|
2774
2487
|
downloader: A Download or Retrieve object
|
|
2775
2488
|
url: URL to download
|
|
2776
|
-
hxltags: Header to HXL hashtag mapping
|
|
2777
2489
|
folder: Folder to which to write file containing rows
|
|
2778
2490
|
filename: Filename of file to write rows
|
|
2779
2491
|
resourcedata: Resource data
|
|
@@ -2814,89 +2526,6 @@ class Dataset(HDXObject):
|
|
|
2814
2526
|
no_empty=no_empty,
|
|
2815
2527
|
)
|
|
2816
2528
|
|
|
2817
|
-
def download_and_generate_resource(
|
|
2818
|
-
self,
|
|
2819
|
-
downloader: BaseDownload,
|
|
2820
|
-
url: str,
|
|
2821
|
-
hxltags: dict[str, str],
|
|
2822
|
-
folder: Path | str,
|
|
2823
|
-
filename: str,
|
|
2824
|
-
resourcedata: dict,
|
|
2825
|
-
header_insertions: Sequence[tuple[int, str]] | None = None,
|
|
2826
|
-
row_function: Callable[[list[str], dict], dict] | None = None,
|
|
2827
|
-
datecol: str | None = None,
|
|
2828
|
-
yearcol: str | None = None,
|
|
2829
|
-
date_function: Callable[[dict], dict | None] | None = None,
|
|
2830
|
-
quickcharts: dict | None = None,
|
|
2831
|
-
**kwargs: Any,
|
|
2832
|
-
) -> tuple[bool, dict]:
|
|
2833
|
-
"""Download url, write rows to csv and create resource, adding to it
|
|
2834
|
-
the dataset. The returned dictionary will contain the resource in the
|
|
2835
|
-
key resource, headers in the key headers and list of rows in the key
|
|
2836
|
-
rows.
|
|
2837
|
-
|
|
2838
|
-
Optionally, headers can be inserted at specific positions. This is
|
|
2839
|
-
achieved using the header_insertions argument. If supplied, it is a
|
|
2840
|
-
list of tuples of the form (position, header) to be inserted. A
|
|
2841
|
-
function is called for each row. If supplied, it takes as arguments:
|
|
2842
|
-
headers (prior to any insertions) and row (which will be in dict or
|
|
2843
|
-
list form depending upon the dict_rows argument) and outputs a modified
|
|
2844
|
-
row.
|
|
2845
|
-
|
|
2846
|
-
The time period can optionally be set by supplying a column in
|
|
2847
|
-
which the date or year is to be looked up. Note that any timezone
|
|
2848
|
-
information is ignored and UTC assumed. Alternatively, a function can
|
|
2849
|
-
be supplied to handle any dates in a row. It should accept a row and
|
|
2850
|
-
should return None to ignore the row or a dictionary which can either
|
|
2851
|
-
be empty if there are no dates in the row or can be populated with
|
|
2852
|
-
keys startdate and/or enddate which are of type timezone-aware
|
|
2853
|
-
datetime. The lowest start date and highest end date are used to set
|
|
2854
|
-
the time period and are returned in the results dictionary in keys
|
|
2855
|
-
startdate and enddate.
|
|
2856
|
-
|
|
2857
|
-
Args:
|
|
2858
|
-
downloader: A Download or Retrieve object
|
|
2859
|
-
url: URL to download
|
|
2860
|
-
hxltags: Header to HXL hashtag mapping
|
|
2861
|
-
folder: Folder to which to write file containing rows
|
|
2862
|
-
filename: Filename of file to write rows
|
|
2863
|
-
resourcedata: Resource data
|
|
2864
|
-
header_insertions: List of (position, header) to insert. Defaults to None.
|
|
2865
|
-
row_function: Function to call for each row. Defaults to None.
|
|
2866
|
-
datecol: Date column for setting time period. Defaults to None (don't set).
|
|
2867
|
-
yearcol: Year column for setting dataset year range. Defaults to None (don't set).
|
|
2868
|
-
date_function: Date function to call for each row. Defaults to None.
|
|
2869
|
-
**kwargs: Any additional args to pass to downloader.get_tabular_rows
|
|
2870
|
-
|
|
2871
|
-
Returns:
|
|
2872
|
-
(True if resource added, dictionary of results)
|
|
2873
|
-
"""
|
|
2874
|
-
warnings.warn(
|
|
2875
|
-
"download_and_generate_resource() is deprecated, use download_generate_resource() instead",
|
|
2876
|
-
DeprecationWarning,
|
|
2877
|
-
)
|
|
2878
|
-
headers, iterator = downloader.get_tabular_rows(
|
|
2879
|
-
url,
|
|
2880
|
-
dict_form=True,
|
|
2881
|
-
header_insertions=header_insertions,
|
|
2882
|
-
row_function=row_function,
|
|
2883
|
-
format="csv",
|
|
2884
|
-
**kwargs,
|
|
2885
|
-
)
|
|
2886
|
-
return self.generate_resource_from_iterable(
|
|
2887
|
-
headers,
|
|
2888
|
-
iterator,
|
|
2889
|
-
hxltags,
|
|
2890
|
-
folder,
|
|
2891
|
-
filename,
|
|
2892
|
-
resourcedata,
|
|
2893
|
-
datecol=datecol,
|
|
2894
|
-
yearcol=yearcol,
|
|
2895
|
-
date_function=date_function,
|
|
2896
|
-
quickcharts=quickcharts,
|
|
2897
|
-
encoding=kwargs.get("encoding", None),
|
|
2898
|
-
)
|
|
2899
|
-
|
|
2900
2529
|
def add_hapi_error(
|
|
2901
2530
|
self,
|
|
2902
2531
|
error_message: str,
|
hdx/data/resource.py
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
"""Resource class containing all logic for creating, checking, and updating resources."""
|
|
2
2
|
|
|
3
3
|
import logging
|
|
4
|
-
import warnings
|
|
5
4
|
from collections.abc import Sequence
|
|
6
5
|
from datetime import datetime
|
|
7
6
|
from pathlib import Path
|
|
@@ -244,13 +243,6 @@ class Resource(HDXObject):
|
|
|
244
243
|
file_format = file_format.lower()
|
|
245
244
|
return file_format
|
|
246
245
|
|
|
247
|
-
def get_file_type(self) -> str | None:
|
|
248
|
-
warnings.warn(
|
|
249
|
-
"get_file_type() is deprecated, use get_format() instead",
|
|
250
|
-
DeprecationWarning,
|
|
251
|
-
)
|
|
252
|
-
return self.get_format()
|
|
253
|
-
|
|
254
246
|
def set_format(self, format: str) -> str:
|
|
255
247
|
"""Set the resource's file type
|
|
256
248
|
|
|
@@ -268,13 +260,6 @@ class Resource(HDXObject):
|
|
|
268
260
|
self.data["format"] = file_format
|
|
269
261
|
return file_format
|
|
270
262
|
|
|
271
|
-
def set_file_type(self, file_type: str) -> str:
|
|
272
|
-
warnings.warn(
|
|
273
|
-
"set_file_type() is deprecated, use set_format() instead",
|
|
274
|
-
DeprecationWarning,
|
|
275
|
-
)
|
|
276
|
-
return self.set_format(file_type)
|
|
277
|
-
|
|
278
263
|
def clean_format(self) -> str:
|
|
279
264
|
"""Clean the resource's format, setting it to None if it is invalid and
|
|
280
265
|
cannot be mapped
|
|
@@ -284,13 +269,6 @@ class Resource(HDXObject):
|
|
|
284
269
|
"""
|
|
285
270
|
return self.set_format(self.data.get("format"))
|
|
286
271
|
|
|
287
|
-
def clean_file_type(self) -> str:
|
|
288
|
-
warnings.warn(
|
|
289
|
-
"clean_file_type() is deprecated, use clean_format() instead",
|
|
290
|
-
DeprecationWarning,
|
|
291
|
-
)
|
|
292
|
-
return self.set_format(self.data.get("format"))
|
|
293
|
-
|
|
294
272
|
def get_file_to_upload(self) -> str | None:
|
|
295
273
|
"""Get the file uploaded
|
|
296
274
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: hdx-python-api
|
|
3
|
-
Version: 6.6.
|
|
3
|
+
Version: 6.6.6
|
|
4
4
|
Summary: HDX Python API for interacting with the Humanitarian Data Exchange
|
|
5
5
|
Project-URL: Homepage, https://github.com/OCHA-DAP/hdx-python-api
|
|
6
6
|
Author: Michael Rans
|
|
@@ -28,10 +28,9 @@ Requires-Python: >=3.10
|
|
|
28
28
|
Requires-Dist: ckanapi>=4.8
|
|
29
29
|
Requires-Dist: defopt>=7.0.0
|
|
30
30
|
Requires-Dist: email-validator
|
|
31
|
-
Requires-Dist: hdx-python-country>=4.
|
|
32
|
-
Requires-Dist: hdx-python-utilities>=4.0.
|
|
31
|
+
Requires-Dist: hdx-python-country>=4.1.1
|
|
32
|
+
Requires-Dist: hdx-python-utilities>=4.0.8
|
|
33
33
|
Requires-Dist: makefun
|
|
34
|
-
Requires-Dist: quantulum3
|
|
35
34
|
Requires-Dist: requests
|
|
36
35
|
Provides-Extra: docs
|
|
37
36
|
Requires-Dist: mkapi; extra == 'docs'
|
|
@@ -53,3 +52,87 @@ For more information, please read the
|
|
|
53
52
|
This library is part of the [Humanitarian Data Exchange](https://data.humdata.org/)
|
|
54
53
|
(HDX) project. If you have humanitarian related data, please upload your datasets to
|
|
55
54
|
HDX.
|
|
55
|
+
|
|
56
|
+
# Development
|
|
57
|
+
|
|
58
|
+
## Environment
|
|
59
|
+
|
|
60
|
+
Development is currently done using Python 3.13. The environment can be created with:
|
|
61
|
+
|
|
62
|
+
```shell
|
|
63
|
+
uv sync
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
This creates a .venv folder with the versions specified in the project's uv.lock file.
|
|
67
|
+
|
|
68
|
+
### Pre-commit
|
|
69
|
+
|
|
70
|
+
pre-commit will be installed when syncing uv. It is run every time you make a git
|
|
71
|
+
commit if you call it like this:
|
|
72
|
+
|
|
73
|
+
```shell
|
|
74
|
+
pre-commit install
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
With pre-commit, all code is formatted according to
|
|
78
|
+
[ruff](https://docs.astral.sh/ruff/) guidelines.
|
|
79
|
+
|
|
80
|
+
To check if your changes pass pre-commit without committing, run:
|
|
81
|
+
|
|
82
|
+
```shell
|
|
83
|
+
pre-commit run --all-files
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
## Packages
|
|
87
|
+
|
|
88
|
+
[uv](https://github.com/astral-sh/uv) is used for package management. If
|
|
89
|
+
you’ve introduced a new package to the source code (i.e. anywhere in `src/`),
|
|
90
|
+
please add it to the `project.dependencies` section of `pyproject.toml` with
|
|
91
|
+
any known version constraints.
|
|
92
|
+
|
|
93
|
+
To add packages required only for testing, add them to the
|
|
94
|
+
`[dependency-groups]`.
|
|
95
|
+
|
|
96
|
+
Any changes to the dependencies will be automatically reflected in
|
|
97
|
+
`uv.lock` with `pre-commit`, but you can re-generate the files without committing by
|
|
98
|
+
executing:
|
|
99
|
+
|
|
100
|
+
```shell
|
|
101
|
+
uv lock --upgrade
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
## Project
|
|
105
|
+
|
|
106
|
+
[uv](https://github.com/astral-sh/uv) is used for project management. The project can be
|
|
107
|
+
built using:
|
|
108
|
+
|
|
109
|
+
```shell
|
|
110
|
+
uv build
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
Linting and syntax checking can be run with:
|
|
114
|
+
|
|
115
|
+
```shell
|
|
116
|
+
uv run ruff check
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
To run the tests and view coverage, execute:
|
|
120
|
+
|
|
121
|
+
```shell
|
|
122
|
+
uv run pytest
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
For the `test_ckan.py` test to run successfully, some configuration is required:
|
|
126
|
+
|
|
127
|
+
1. The environment variable HDX_KEY_TEST needs to contain a valid key from the HDX demo
|
|
128
|
+
server at https://demo.data-humdata-org.ahconu.org/
|
|
129
|
+
1. Authentication details for Google Sheets need to be obtained and either saved in a
|
|
130
|
+
file named `.gsheet_auth.json` in the home directory (~) or placed in an environment
|
|
131
|
+
variable `GSHEET_AUTH`. The file is preferred for Windows systems since adding such a
|
|
132
|
+
long text string to an environment variable in Windows is challenging.
|
|
133
|
+
|
|
134
|
+
## Documentation
|
|
135
|
+
|
|
136
|
+
The documentation, including API documentation, is generated using ReadtheDocs and
|
|
137
|
+
MkDocs with Material. As you change the source code, remember to update the
|
|
138
|
+
documentation at `documentation/index.md`.
|
|
@@ -1,23 +1,20 @@
|
|
|
1
1
|
hdx/api/__init__.py,sha256=11ozJKiUsqDCZ3_mcAHhGYUyGK_Unl54djVSBBExFB4,59
|
|
2
|
-
hdx/api/_version.py,sha256=
|
|
2
|
+
hdx/api/_version.py,sha256=ExjJQQs0P3ByYbVeKqojfK1IwhKHopRm142FJqldcBU,520
|
|
3
3
|
hdx/api/configuration.py,sha256=9WvuVI-3th2Dv70_t_4VoyrlJwB_9qUSN5_zUKbp4sQ,23805
|
|
4
4
|
hdx/api/hdx_base_configuration.yaml,sha256=9yJYroZwoyMQo6tcxGfWHWvYahnTma7Szx0J5Z378z4,2173
|
|
5
5
|
hdx/api/locations.py,sha256=blnqOjEASvh0mWgmo2iEE7ES_xOuRwQo3fOMqTPV_P0,4139
|
|
6
6
|
hdx/api/remotehdx.py,sha256=IjKwP3jutlkrjvWCJPP7YM-xzg3pP_QVmiJfblllBY0,2079
|
|
7
7
|
hdx/api/utilities/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
8
|
-
hdx/api/utilities/dataset_title_helper.py,sha256=b3Q-ycVC6dtspgJDUqhY7GK7XpXPVeFtlzg_T5gz4Ts,9110
|
|
9
8
|
hdx/api/utilities/date_helper.py,sha256=sXxjRtXz-2rpBVZBbJAk80gfjpYKl_AtHplWr-rniIk,6396
|
|
10
9
|
hdx/api/utilities/filestore_helper.py,sha256=0FcUpGHpYlAzlNDpDxdXbSqV6VHFfGCLakCmK37hieU,6917
|
|
11
10
|
hdx/api/utilities/hdx_error_handler.py,sha256=x1rb2qBO0C6CJjqToSFj_XB1F3pf3nHpA8otxXfTFzA,8711
|
|
12
11
|
hdx/api/utilities/hdx_state.py,sha256=KzrK0A-H5R6cRPqwF4d23_S_zfIa1ow4K5u46FesGmY,2442
|
|
13
12
|
hdx/api/utilities/url_utils.py,sha256=Pb4r-BAqIHYmeoB_i7QjutDp6nOAUh_OHvCtiNLda3Y,4534
|
|
14
13
|
hdx/data/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
15
|
-
hdx/data/dataset.py,sha256=
|
|
16
|
-
hdx/data/hdx_datasource_topline.yaml,sha256=kBsb7UPl9RGkPbXfJ-OBQBzIse57-o0T41sbOCd2KVg,366
|
|
14
|
+
hdx/data/dataset.py,sha256=0gS7tQSswTgG0g8YeXg0FL8pFVkocBQSRPFpQAurN00,99455
|
|
17
15
|
hdx/data/hdxobject.py,sha256=_jlwYjb_CcjaXsTXObknMfq-OiNOcpJfLJ9b1vKV1Zo,27494
|
|
18
|
-
hdx/data/indicator_resource_view_template.yaml,sha256=iww2EjGSJdUpN3vhsUEva6ohrXjS-tjR56K5Aly0Xo4,2915
|
|
19
16
|
hdx/data/organization.py,sha256=JHlCl50g1nh65DzN75OgMdvEcT_wfSIPLkhI5Ms-u8g,10588
|
|
20
|
-
hdx/data/resource.py,sha256=
|
|
17
|
+
hdx/data/resource.py,sha256=5j6QK5_J-UFKCT3jZVUZ66VgGsOcCssmGy4jpqdb1N4,34027
|
|
21
18
|
hdx/data/resource_matcher.py,sha256=Yzk9pWa4zk1p09m6zcZMj0XZf5Y16Pa3KToOdUt7rP8,4247
|
|
22
19
|
hdx/data/resource_view.py,sha256=GcYkh0iDAe6_m2ErY_A6i3Z7ZFX949eGt1TLnLZv_8s,7346
|
|
23
20
|
hdx/data/showcase.py,sha256=vnRZ45xd9Ct65eFQkWZtMIJPFifZfKOrH4obO5D5Pvc,14062
|
|
@@ -27,7 +24,7 @@ hdx/facades/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
|
27
24
|
hdx/facades/infer_arguments.py,sha256=DvyDuozfOPmoaV7HjhQySojGIrMWW9nos31auQBVVl8,4032
|
|
28
25
|
hdx/facades/keyword_arguments.py,sha256=aVapHCfqBO6H7xxBgTdK9ISdgrBFsNypPDWihfQ3edU,1125
|
|
29
26
|
hdx/facades/simple.py,sha256=QWsBkaHG_UM6X3Zu2u3LWVOQzeR24q-EUClrfsL_mkM,1064
|
|
30
|
-
hdx_python_api-6.6.
|
|
31
|
-
hdx_python_api-6.6.
|
|
32
|
-
hdx_python_api-6.6.
|
|
33
|
-
hdx_python_api-6.6.
|
|
27
|
+
hdx_python_api-6.6.6.dist-info/METADATA,sha256=o4V01ehlDyD2YzWP0EbQ_hRuMnOoyXuW_etzGWA-kTk,4945
|
|
28
|
+
hdx_python_api-6.6.6.dist-info/WHEEL,sha256=QccIxa26bgl1E6uMy58deGWi-0aeIkkangHcxk2kWfw,87
|
|
29
|
+
hdx_python_api-6.6.6.dist-info/licenses/LICENSE,sha256=wc-4GpMn-ODs-U_bTe1YCiPVgvcjzrpYOx2wPuyAeII,1079
|
|
30
|
+
hdx_python_api-6.6.6.dist-info/RECORD,,
|
|
@@ -1,254 +0,0 @@
|
|
|
1
|
-
"""Helper to the Dataset class for handling processing dataset titles."""
|
|
2
|
-
|
|
3
|
-
import logging
|
|
4
|
-
import re
|
|
5
|
-
from datetime import datetime, timedelta
|
|
6
|
-
from re import Match
|
|
7
|
-
from string import punctuation, whitespace
|
|
8
|
-
|
|
9
|
-
from dateutil.parser import ParserError
|
|
10
|
-
from hdx.utilities.dateparse import parse_date, parse_date_range
|
|
11
|
-
from hdx.utilities.text import (
|
|
12
|
-
PUNCTUATION_MINUS_BRACKETS,
|
|
13
|
-
remove_end_characters,
|
|
14
|
-
remove_from_end,
|
|
15
|
-
remove_string,
|
|
16
|
-
)
|
|
17
|
-
from quantulum3 import parser
|
|
18
|
-
|
|
19
|
-
logger = logging.getLogger(__name__)
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
class DatasetTitleHelper:
|
|
23
|
-
YEAR_PATTERN = re.compile(r"([12]\d\d\d)")
|
|
24
|
-
VERSION_PATTERN = re.compile(r"(\d)?(\d)?\d")
|
|
25
|
-
YEAR_RANGE_PATTERN = re.compile(
|
|
26
|
-
r"([12]\d\d\d)(/(\d{1,2}))?(-| & | and )([12]\d\d\d)"
|
|
27
|
-
)
|
|
28
|
-
YEAR_RANGE_PATTERN2 = re.compile(r"([12]\d\d\d)([/-])(\d{1,2})")
|
|
29
|
-
PUNCTUATION_PATTERN = re.compile(rf"[{punctuation}]")
|
|
30
|
-
EMPTY_BRACKET_PATTERN = re.compile(r"(\s?\(\s*\)\s?)")
|
|
31
|
-
WORD_RIGHT_BRACKET_PATTERN = re.compile(r"\b(\s*)(\w{2,})\b\)")
|
|
32
|
-
DATE_INTRO_WORDS = ["on", "at", "for", "of", "in"]
|
|
33
|
-
|
|
34
|
-
@classmethod
|
|
35
|
-
def fuzzy_match_dates_in_title(
|
|
36
|
-
cls,
|
|
37
|
-
title: str,
|
|
38
|
-
ranges: list[tuple[datetime, datetime]],
|
|
39
|
-
ignore_wrong_years: list[int],
|
|
40
|
-
) -> str:
|
|
41
|
-
"""
|
|
42
|
-
Fuzzy match dates in title appending to ranges
|
|
43
|
-
|
|
44
|
-
Args:
|
|
45
|
-
title: Title to parse
|
|
46
|
-
ranges: List of date ranges found so far
|
|
47
|
-
ignore_wrong_years: Numbers identified as years that probably are not years
|
|
48
|
-
|
|
49
|
-
Returns:
|
|
50
|
-
Title with dates removed
|
|
51
|
-
|
|
52
|
-
"""
|
|
53
|
-
for quant in parser.parse(title):
|
|
54
|
-
if quant.unit.name == "dimensionless":
|
|
55
|
-
continue
|
|
56
|
-
ignore_wrong_years.append(int(quant.value))
|
|
57
|
-
for match in cls.YEAR_PATTERN.finditer(title):
|
|
58
|
-
year = match.group(0)
|
|
59
|
-
if int(year) in ignore_wrong_years:
|
|
60
|
-
continue
|
|
61
|
-
start = match.start()
|
|
62
|
-
end = match.end()
|
|
63
|
-
stringlr = title[max(start - 13, 0) : end]
|
|
64
|
-
fuzzylr = {}
|
|
65
|
-
startdatelr = None
|
|
66
|
-
enddatelr = None
|
|
67
|
-
deltalr = timedelta(days=1000)
|
|
68
|
-
try:
|
|
69
|
-
startdatelr, enddatelr = parse_date_range(
|
|
70
|
-
stringlr,
|
|
71
|
-
fuzzy=fuzzylr,
|
|
72
|
-
zero_time=True,
|
|
73
|
-
max_endtime=True,
|
|
74
|
-
)
|
|
75
|
-
if startdatelr and enddatelr:
|
|
76
|
-
deltalr = enddatelr - startdatelr
|
|
77
|
-
except ParserError:
|
|
78
|
-
pass
|
|
79
|
-
fuzzyrl = {}
|
|
80
|
-
stringrl = title[start : min(end + 13, len(title))]
|
|
81
|
-
startdaterl = None
|
|
82
|
-
enddaterl = None
|
|
83
|
-
deltarl = timedelta(days=1000)
|
|
84
|
-
try:
|
|
85
|
-
startdaterl, enddaterl = parse_date_range(
|
|
86
|
-
stringrl,
|
|
87
|
-
fuzzy=fuzzyrl,
|
|
88
|
-
zero_time=True,
|
|
89
|
-
max_endtime=True,
|
|
90
|
-
)
|
|
91
|
-
if startdaterl and enddaterl:
|
|
92
|
-
deltarl = enddaterl - startdaterl
|
|
93
|
-
except ParserError:
|
|
94
|
-
pass
|
|
95
|
-
if startdatelr and deltalr <= deltarl:
|
|
96
|
-
date_components = fuzzylr["date"]
|
|
97
|
-
ranges.append((startdatelr, enddatelr))
|
|
98
|
-
elif startdaterl:
|
|
99
|
-
date_components = fuzzyrl["date"]
|
|
100
|
-
ranges.append((startdaterl, enddaterl))
|
|
101
|
-
else:
|
|
102
|
-
date_components = year
|
|
103
|
-
ranges.append(parse_date_range(year, zero_time=True, max_endtime=True))
|
|
104
|
-
newtitle = title
|
|
105
|
-
for date_component in date_components:
|
|
106
|
-
newtitle = remove_string(
|
|
107
|
-
newtitle, date_component, PUNCTUATION_MINUS_BRACKETS
|
|
108
|
-
)
|
|
109
|
-
logger.info(f"Removing date from title: {title} -> {newtitle}")
|
|
110
|
-
title = newtitle
|
|
111
|
-
try:
|
|
112
|
-
fuzzy = {}
|
|
113
|
-
startdate, enddate = parse_date_range(
|
|
114
|
-
title,
|
|
115
|
-
fuzzy=fuzzy,
|
|
116
|
-
zero_time=True,
|
|
117
|
-
max_endtime=True,
|
|
118
|
-
)
|
|
119
|
-
datestrs = fuzzy["date"]
|
|
120
|
-
if (
|
|
121
|
-
startdate.date() == enddate.date() and len(datestrs) == 1
|
|
122
|
-
): # only accept dates where day, month and year are
|
|
123
|
-
# all together not split throughout the string and where the date is a precise day not a range
|
|
124
|
-
date_component = datestrs[0]
|
|
125
|
-
index = title.find(date_component) - 1
|
|
126
|
-
if index >= 0 and title[index].lower() == "v":
|
|
127
|
-
return title
|
|
128
|
-
ranges.append((startdate, enddate))
|
|
129
|
-
newtitle = remove_string(
|
|
130
|
-
title, date_component, PUNCTUATION_MINUS_BRACKETS
|
|
131
|
-
)
|
|
132
|
-
logger.info(f"Removing date from title: {title} -> {newtitle}")
|
|
133
|
-
title = newtitle
|
|
134
|
-
except (ParserError, OverflowError):
|
|
135
|
-
pass
|
|
136
|
-
|
|
137
|
-
return title
|
|
138
|
-
|
|
139
|
-
@classmethod
|
|
140
|
-
def get_month_year_in_slash_range(
|
|
141
|
-
cls, match: Match, ignore_wrong_years: list[int]
|
|
142
|
-
) -> tuple[int | None, int | None, int | None]:
|
|
143
|
-
"""
|
|
144
|
-
Get year(s) and month from slash range eg. 2007/08. If second value is between 1 and 12, take it as a month.
|
|
145
|
-
|
|
146
|
-
Args:
|
|
147
|
-
match (Match): Match object
|
|
148
|
-
ignore_wrong_years: Numbers identified as years that probably are not years
|
|
149
|
-
|
|
150
|
-
Returns:
|
|
151
|
-
First year, first month, second year
|
|
152
|
-
|
|
153
|
-
"""
|
|
154
|
-
first_year_str = match.group(1)
|
|
155
|
-
first_year = int(first_year_str)
|
|
156
|
-
if first_year in ignore_wrong_years:
|
|
157
|
-
return None, None, None
|
|
158
|
-
two_digits_str = match.group(3)
|
|
159
|
-
if two_digits_str:
|
|
160
|
-
two_digits = int(two_digits_str)
|
|
161
|
-
if 1 <= two_digits <= 12:
|
|
162
|
-
return first_year, two_digits, None
|
|
163
|
-
else:
|
|
164
|
-
second_year = int(f"{first_year_str[:2]}{two_digits_str}")
|
|
165
|
-
if second_year > first_year:
|
|
166
|
-
return first_year, None, second_year
|
|
167
|
-
else:
|
|
168
|
-
ignore_wrong_years.append(first_year)
|
|
169
|
-
return None, None, None
|
|
170
|
-
else:
|
|
171
|
-
return first_year, None, None
|
|
172
|
-
|
|
173
|
-
@classmethod
|
|
174
|
-
def get_dates_from_title(
|
|
175
|
-
cls, title: str
|
|
176
|
-
) -> tuple[str, list[tuple[datetime, datetime]]]:
|
|
177
|
-
"""
|
|
178
|
-
Get dataset dates (start and end dates in a list) from title and clean title of dates
|
|
179
|
-
|
|
180
|
-
Args:
|
|
181
|
-
title: Title to get date from and clean
|
|
182
|
-
|
|
183
|
-
Returns:
|
|
184
|
-
Cleaned title, list of start and end dates
|
|
185
|
-
|
|
186
|
-
"""
|
|
187
|
-
ranges = []
|
|
188
|
-
ignore_wrong_years = []
|
|
189
|
-
for match in cls.YEAR_RANGE_PATTERN.finditer(title):
|
|
190
|
-
(
|
|
191
|
-
first_year,
|
|
192
|
-
first_month,
|
|
193
|
-
second_year,
|
|
194
|
-
) = cls.get_month_year_in_slash_range(match, ignore_wrong_years)
|
|
195
|
-
if first_year is None:
|
|
196
|
-
continue
|
|
197
|
-
if first_month is None:
|
|
198
|
-
first_month = 1
|
|
199
|
-
startdate = parse_date(
|
|
200
|
-
f"{first_year}-{first_month}-01",
|
|
201
|
-
"%Y-%m-%d",
|
|
202
|
-
zero_time=True,
|
|
203
|
-
)
|
|
204
|
-
enddate = parse_date(
|
|
205
|
-
f"{match.group(5)}-12-31",
|
|
206
|
-
"%Y-%m-%d",
|
|
207
|
-
max_time=True,
|
|
208
|
-
)
|
|
209
|
-
ranges.append((startdate, enddate))
|
|
210
|
-
newtitle = remove_string(title, match.group(0))
|
|
211
|
-
logger.info(f"Removing date range from title: {title} -> {newtitle}")
|
|
212
|
-
title = newtitle
|
|
213
|
-
|
|
214
|
-
for match in cls.YEAR_RANGE_PATTERN2.finditer(title):
|
|
215
|
-
(
|
|
216
|
-
first_year,
|
|
217
|
-
first_month,
|
|
218
|
-
second_year,
|
|
219
|
-
) = cls.get_month_year_in_slash_range(match, ignore_wrong_years)
|
|
220
|
-
if first_year is None or second_year is None:
|
|
221
|
-
continue
|
|
222
|
-
startdate = parse_date(
|
|
223
|
-
f"{first_year}-01-01",
|
|
224
|
-
"%Y-%m-%d",
|
|
225
|
-
zero_time=True,
|
|
226
|
-
)
|
|
227
|
-
enddate = parse_date(
|
|
228
|
-
f"{second_year}-12-31",
|
|
229
|
-
"%Y-%m-%d",
|
|
230
|
-
max_time=True,
|
|
231
|
-
)
|
|
232
|
-
ranges.append((startdate, enddate))
|
|
233
|
-
newtitle = remove_string(title, match.group(0))
|
|
234
|
-
logger.info(f"Removing date range from title: {title} -> {newtitle}")
|
|
235
|
-
title = newtitle
|
|
236
|
-
|
|
237
|
-
title = cls.fuzzy_match_dates_in_title(title, ranges, ignore_wrong_years)
|
|
238
|
-
|
|
239
|
-
for match in cls.WORD_RIGHT_BRACKET_PATTERN.finditer(title):
|
|
240
|
-
word = match.group(2)
|
|
241
|
-
if word in cls.DATE_INTRO_WORDS:
|
|
242
|
-
title = title.replace(match.group(0), ")")
|
|
243
|
-
|
|
244
|
-
for match in cls.EMPTY_BRACKET_PATTERN.finditer(title):
|
|
245
|
-
title = title.replace(match.group(0), " ")
|
|
246
|
-
title = remove_end_characters(
|
|
247
|
-
title, f"{PUNCTUATION_MINUS_BRACKETS}{whitespace}"
|
|
248
|
-
)
|
|
249
|
-
title = remove_from_end(
|
|
250
|
-
title,
|
|
251
|
-
["as of"] + cls.DATE_INTRO_WORDS,
|
|
252
|
-
"Removing - from title: %s -> %s",
|
|
253
|
-
)
|
|
254
|
-
return title, sorted(ranges)
|
|
@@ -1,30 +0,0 @@
|
|
|
1
|
-
#Topline configuration
|
|
2
|
-
schema:
|
|
3
|
-
-
|
|
4
|
-
id: code
|
|
5
|
-
type: text
|
|
6
|
-
-
|
|
7
|
-
id: title
|
|
8
|
-
type: text
|
|
9
|
-
-
|
|
10
|
-
id: value
|
|
11
|
-
type: float
|
|
12
|
-
-
|
|
13
|
-
id: latest_date
|
|
14
|
-
type: timestamp
|
|
15
|
-
-
|
|
16
|
-
id: source
|
|
17
|
-
type: text
|
|
18
|
-
-
|
|
19
|
-
id: source_link
|
|
20
|
-
type: text
|
|
21
|
-
-
|
|
22
|
-
id: notes
|
|
23
|
-
type: text
|
|
24
|
-
-
|
|
25
|
-
id: explore
|
|
26
|
-
type: text
|
|
27
|
-
-
|
|
28
|
-
id: units
|
|
29
|
-
type: text
|
|
30
|
-
primary_key: code
|
|
@@ -1,4 +0,0 @@
|
|
|
1
|
-
description: ""
|
|
2
|
-
title: "Quick Charts"
|
|
3
|
-
view_type: "hdx_hxl_preview"
|
|
4
|
-
hxl_preview_config: '{"configVersion":5,"bites":[{"tempShowSaveCancelButtons":false,"ingredient":{"aggregateColumn":"AGGREGATE_COL_1","valueColumn":"VALUE_COL_1","aggregateFunction":"sum","dateColumn":"DATE_COL_1","comparisonValueColumn":null,"comparisonOperator":null,"filters":{"filterWith":[{"CODE_COL_1":"CODE_VALUE_1"}]},"description":"DESCRIPTION_VALUE_1"},"type":"timeseries","errorMsg":null,"computedProperties":{"explainedFiltersMap":{"remove empty valued rows":{"filterWith":[{"VALUE_COL_1":"is not empty"}],"filterWithout":[]}},"pieChart":false,"title":"Sum of Value by Year","dataTitle":"UNIT_VALUE_1"},"uiProperties":{"swapAxis":true,"showGrid":true,"color":"#0077ce","sortingByValue1":"DESC","sortingByCategory1":null,"showPoints":true,"internalColorPattern":["#1ebfb3","#0077ce","#f2645a","#9C27B0"],"title":"TITLE_VALUE_1","dateFormat":"DATE_FORMAT_1"},"dataProperties":{},"displayCategory":"Timeseries","hashCode":498306664},{"tempShowSaveCancelButtons":false,"ingredient":{"aggregateColumn":"AGGREGATE_COL_2","valueColumn":"VALUE_COL_2","aggregateFunction":"sum","dateColumn":"DATE_COL_2","comparisonValueColumn":null,"comparisonOperator":null,"filters":{"filterWith":[{"CODE_COL_2":"CODE_VALUE_2"}]},"description":"DESCRIPTION_VALUE_2"},"type":"timeseries","errorMsg":null,"computedProperties":{"explainedFiltersMap":{"remove empty valued rows":{"filterWith":[{"VALUE_COL_2":"is not empty"}],"filterWithout":[]}},"pieChart":false,"title":"Sum of Value by Year","dataTitle":"UNIT_VALUE_2"},"uiProperties":{"swapAxis":true,"showGrid":true,"color":"#0077ce","sortingByValue1":"DESC","sortingByCategory1":null,"showPoints":true,"internalColorPattern":["#1ebfb3","#0077ce","#f2645a","#9C27B0"],"title":"TITLE_VALUE_2","dateFormat":"DATE_FORMAT_2"},"dataProperties":{},"displayCategory":"Timeseries","hashCode":498306664},{"tempShowSaveCancelButtons":false,"ingredient":{"aggregateColumn":"AGGREGATE_COL_3","valueColumn":"VALUE_COL_3","aggregateFunction":"sum","dateColumn":"DATE_COL_3","comparisonValueColumn":null,"comparisonOperator":null,"filters":{"filterWith":[{"CODE_COL_3":"CODE_VALUE_3"}]},"description":"DESCRIPTION_VALUE_3"},"type":"timeseries","errorMsg":null,"computedProperties":{"explainedFiltersMap":{"remove empty valued rows":{"filterWith":[{"VALUE_COL_3":"is not empty"}],"filterWithout":[]}},"pieChart":false,"title":"Sum of Value by Year","dataTitle":"UNIT_VALUE_3"},"uiProperties":{"swapAxis":true,"showGrid":true,"color":"#0077ce","sortingByValue1":"DESC","sortingByCategory1":null,"showPoints":true,"internalColorPattern":["#1ebfb3","#0077ce","#f2645a","#9C27B0"],"title":"TITLE_VALUE_3","dateFormat":"DATE_FORMAT_3"},"dataProperties":{},"displayCategory":"Timeseries","hashCode":498306664}],"recipeUrl":"https://raw.githubusercontent.com/mcarans/hxl-recipes/dev/recipes/historic-values-filtered/recipe.json"}'
|
|
File without changes
|